<?php

namespace App\Modules\Bookings;

use Illuminate\Support\Facades\Mail;
use Exception;
use Illuminate\Support\Facades\Log;
use App\Facades\DBconfig;
use App\Models\User;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Cache;
use Illuminate\Foundation\Bus\DispatchesJobs;
use App\Jobs\sendRepairUpdateToMobile;
use App\Models\Status;
use App\Models\Severity;

class TaskObserver
{
    use DispatchesJobs;

    public function saved($task)
    {
        // Clear all the caches when a task gets saved (created/updated)
        try {
            // Only use cache tags if the driver supports it (Redis, Memcached)
            if (in_array(config('cache.default'), ['redis', 'memcached'])) {
                Cache::tags(['tasks'])->flush();
            } else {
                // For drivers that don't support tagging, clear all cache
                Cache::flush();
            }
        } catch (\Exception $e) {
            // Fallback: just clear all cache if tagging fails
            Cache::flush();
        }
    }

    public function creating($model)
    {
        $this->assignDefaultUsers($model);
    }

    public function saving($model)
    {
        $this->checkTaskLocked($model);
        $this->checkTemplateCompleted($model);
        $this->checkCompleted($model);
        $this->checkDelivery($model);
        $this->checkCollected($model);
        $this->logFieldChanges($model);
        $this->notifySubscribers($model);
        $this->notifyManagers($model);
        $this->createFollowUpTask($model);
        $this->createQATask($model);
        $this->updateAssignedToForQA($model);
        $this->deleteQATask($model);
        $this->deleteCallReminderWhenCollected($model);
    }

    public function updating($model)
    {
        $this->stopIfHasParts($model);
    }

    public function updated($model)
    {
        $this->cancelExchangeCalendarEvent($model);
        $this->detectStatusChange($model);
    }

    public function deleting($model)
    {
        $this->checkTaskLocked($model);
    }

    /**
     * ------------------------------------------------
     * PROTECTED FUNCTIONS BELOW
     * ------------------------------------------------.
     */

    protected function detectStatusChange($model){

        $changed_rows = $model->getDirty();

        if(array_key_exists('status_id',$changed_rows)){
            $this->dispatch(new sendRepairUpdateToMobile($model));
        }

    }

    /**
     * Checks to see if the task has been invoiced or marked as paid, in which case it cannot be updated.
     *
     * @return void
     */
    protected function checkTaskLocked($model)
    {
        if ($model->isDirty('labour')) {
            if ($model->invoiced) {
                throw new Exception('This task has already been invoiced. You cannot modify its contents.');
            }

            if ($model->paid) {
                throw new Exception('This task has already been paid. You cannot modify its contents.');
            }
        }
    }

    /**
     * Checks to see if the task templates have been completed, otherwise it cannot be marked as qa.
     *
     * @return void
     */
    protected function checkTemplateCompleted($model)
    {
        if ($model->isDirty('status_id')) {
            //If new status is Quality Control check if the templates have been fill completely
            if ($model->status_id == 9) {
                if (! $model->completedTemplates()) {
                    throw new Exception('You must complete all the template tasks before you can close this job.');
                }
            }
        }
    }

    /**
     * Update the status & close / open the job
     * This is essetial as it automatically updates
     * the completed & completed_at fields based on the status
     * Also it checks contract customers and marks them as invoiced & paid.
     *
     * @return null
     */
    protected function checkCompleted($model)
    {
        $status = Status::findOrFail($model->status_id);
        if ($status->id) {
            if ($model->completed != $status->completed) {
                $model->completed = $status->completed;
            }

            if ($model->completed) {
                if ($model->isDirty('completed')) {
                    $model->completed_at = date('Y-m-d H:i:s');
                }
                // Register for feedback
                $model->registerFeedback();
                $this->autoCloseContractJobs($model);
            }
        }
    }

    /**
     * Adds a timestamp collected_at if the status is changed to collected.
     *
     * @param  App\Modules\Bookings\Task $model
     *
     * @return null
     */
    protected function checkCollected($model)
    {
        if ($model->isDirty('status_id') && $model->status_id == 11) {
            $model->collected_at = date('Y-m-d H:i:s');
        }
    }

    /**
     * Checks to see if a task has been marked as delivered
     * and if it is completed. If it is not completed then it will
     * close the job.
     *
     * @return null
     */
    protected function checkDelivery($model)
    {
        if ($model->isDirty('delivered') && $model->delivered == 1 && $model->completed == 0) {
            $model->completed = 1;
            $model->completed_at = date('Y-m-d H:i:s');
            $model->status_id = 13; // Delivered status
        } elseif ($model->isDirty('status_id') && $model->status_id == 13) {
            if (! $model->completed) {
                $model->completed = 1;
                $model->completed_at = date('Y-m-d H:i:s');
            }
            $model->delivered = 1;
        }
    }

    /**
     * We are checking to see if the task is a remote support
     * and if the customer is on a contract in which case we will
     * mark it as paid and try to avoid cluttering the accounts page.
     *
     * @return null
     */
    protected function autoCloseContractJobs($model)
    {
        if ($model->type == 'support' && $model->labour == 0 && $model->parts_total == 0 && ($model->client->contract_id == 1 || $model->client->contract_id == 2)) {
            // No labour or parts and the client is on a remote or full contract
            $model->invoiced = 1;
            $model->paid = 1;
            // We are updating this values only if they are not set already
            if (! $model->paid_at) {
                $model->paid_at = date('Y-m-d H:i:s');
            }
            if (! $model->invoice_number) {
                $model->invoice_number = 1;
            }
        }
    }

    /**
     * Notify all the subscribed users when a task changes status
     * or the assigned user.
     *
     * @param  $model
     *
     * @return null
     */
    protected function notifySubscribers($model)
    {
        if ($model->isDirty('assigned_to') || $model->isDirty('status_id')) {
            // Notify users that the assigned user or the task status has changed
            try {
                $subscribers = $model->subscribers()->get();
            } catch (Exception $e) {
                Log::error($e);
                $subscribers = [];
            }

            // Compose a list of emails
            $CC = [];
            foreach ($subscribers as $subscriber) {
                $CC[] = $subscriber->email;
            }
            try {
		    if($model->client->id == 745 && $model->site_id == 759 && $model->type=='support' && $model->status_id == 10 && $model->isDirty('status_id'))
		    {
			    $CC[] = "imandtservicedeskadmin@ouh.nhs.uk";
		    }
            } catch (Exception $e) {
                Log::error($e);
            }
            // Get the first email as the main recipient...
            $send_to = array_shift($CC);

            if ($send_to) {
                try {
                    if ($model->client_type == 'client') {
                        $model->load('site_user');
                    }
                    $data = ['task' => $model];

                    // Get the user changes
                    if ($model->isDirty('assigned_to')) {
                        $data['assigned_change'] = new \AssignedChange([
                                'related_id'   => $model->id,
                                'related_type' => 'task',
                                'old_user'     => $model->getOriginal('assigned_to'),
                                'new_user'     => $model->assigned_to,
                                'changed_by'   => getAuthUserId(),
                            ]);
                    }
                    if ($model->isDirty('status_id')) {
                        $data['status_change'] = new \StatusChange([
                                'related_id'   => $model->id,
                                'related_type' => 'task',
                                'old_status'   => $model->getOriginal('status_id'),
                                'new_status'   => $model->status_id,
                                'changed_by'   => getAuthUserId(),
                            ]);
                    }

                    // Attempt to send the mail using global SMTP configuration
                    \App\Seraph\Services\GlobalMailerService::send('emails.tasks.subscriber-changes', $data, function ($message) use ($model, $send_to, $CC) {
                        // Configure the email options
                        $message->to($send_to)->subject('Subscribed task updated: '.$model->title.' ['.$model->id.']');
                        if (count($CC)) {
                            $message->cc($CC);
                        }
                    }, null, null, false); // false = no CC/BCC from SMTP settings for notifications
                } catch (Exception $e) {
                    Log::error($e);
                }
            }
        }
    }

    protected function notifyManagers($model)
    {
        try {
            if ($model->type == 'repair' && $model->isDirty('completed') && $model->completed && $model->assigned_to && DBconfig::get('emails.repairs.completed_enabled')) {
                $managers = DBconfig::get('emails.repairs.managers', []);
                $send_to = count($managers) ? array_shift($managers) : false;

                if ($send_to) {
                    \App\Seraph\Services\GlobalMailerService::send('emails.tasks.completed', ['task' => $model], function ($message) use ($model, $send_to) {
                        // Configure the email options
                        $message->to($send_to)->subject(trim($model->user->name).' has completed a repair ['.$model->id.']');
                    }, null, null, false, $managers); // Pass remaining managers as additional CC
                }
            }
        } catch (Exception $e) {
            Log::error($e);
        }
    }

    /**
     * Assigns default users for sale leads and orders.
     *
     * @return null
     */
    protected function assignDefaultUsers($model)
    {
        if ($model->type == 'sale_lead') {
            if (! $model->assigned_to && User::find(5)) {
                $model->assigned_to = 5;
            } // Assign it to Raphael
        }
    }

    /**
     * Add "Canceled" to the title of a task when it has been cancelled.
     *
     * @param App\Modules\Bookings\Task $model
     *
     * @return void
     */
    private function cancelExchangeCalendarEvent($model)
    {
        try {
            if ($model->type == 'onsite' && $model->isDirty('status_id') && $model->exchange_id && ($model->status_id == 8 || $model->getOriginal('status_id') == 8)) {
                $item = \Ews::getItem($model->exchange_id);
                if ($item) {
                    $newTilte = $model->status_id == 8 ?  'CANCELLED - '.$item->Subject : str_replace('CANCELLED - ', '', $item->Subject);
                    \Ews::editTitle($item->ItemId->Id, $item->ItemId->ChangeKey, $newTilte);
                }
            }
        } catch (Exception $e) {
            Log::error($e);
        }
    }

    /**
     * Create a task to followup the previous one every time an onsite task has been closed.
     *
     * @param App\Modules\Bookings\Task $model
     *
     * @return null
     */
    protected function createFollowUpTask($model)
    {
        // Do not create a followup if the client is exempt from it
        if ($model->client_type == 'client' && $model->client->followup_exempt) {
            return;
        }

        try {
            if ($model->isDirty('completed') && $model->completed && $model->type == 'onsite') {
                if ($model->followup_task()->where('completed', 0)->count() == 0) {
                    $followUpCreator = App::make('App\Modules\Bookings\FollowUpTaskCreator');
                    $followUpCreator->create($model);
                }
            }
        } catch (Exception $e) {
            Log::error($e);
        }
    }

    /**
     * Create the QA task to the previous one every time the status of a task chenge to Quality Assurance.
     *
     * @param App\Modules\Bookings\Task $model
     *
     * @return null
     */
    protected function createQATask($model)
    {
        if ($model->isDirty('status_id') && $model->status_id == 9) {
            // Note: Validation for assigned_to and antivirus_state is now handled in the controller
            // to prevent 500 errors and show proper validation messages
            
            try {
                if ($model->qa_tasks()->where('completed', 0)->count() == 0) {
                    $qaCreator = App::make('App\Modules\Bookings\QATaskCreator');
                    $qaCreator->create($model);
                }
            } catch (Exception $e) {
                Log::error($e);
            }
        }
    }

    /**
     * Delete the QA task related to a task when it come back to work in progress.
     *
     * @param App\Modules\Bookings\Task $model
     *
     * @return null
     */
    protected function deleteQATask($model)
    {
        if ($model->isDirty('status_id') && $model->status_id == 4) {
            // Do not change the status to QA if the task is assigned to no one,
            $model->qa_tasks()->where('completed', 0)->delete();
            $model->qa_tasks()->where('needs_calling', 1)->update(['needs_calling' => 0]);
        }
    }

    /**
     * For QA & FollowUp tasks, update the assigned to value
     * to the user that has pressed the button to update
     * the task.
     *
     * @param  App\Modules\Bookings\Task $model
     *
     * @return null
     */
    protected function updateAssignedToForQA($model)
    {
        if (array_search($model->type, ['qa', 'followup']) !== false && ($model->isDirty('passed_qa') || $model->isDirty('satisfied_client'))) {
            // Get the original job that the QA is for
            if ($model->qa_id) {
                $task = Task::findOrFail($model->qa_id);

                if (getAuthUserId() == $task->assigned_to) {
                    throw new Exception('You cannot QA your own jobs!');
                }
            }

            $model->assigned_to = getAuthUserId();
        }
    }

    /**
     * Changes needs_calling to be false if the task is marked with status collected.
     *
     * @param  App\Modules\Bookings\Task $model
     *
     * @return null
     */
    protected function deleteCallReminderWhenCollected($model)
    {
        //If status is collected
        if ($model->isDirty('status_id') && $model->status_id == Status::TASKS_COLLECTED) {
            $model->qa_tasks()->where('needs_calling', 1)->update(['needs_calling' => 0]);
        }
    }

    /**
     * Avoid task cancellation if it has parts.
     *
     * @param  App\Modules\Bookings\Task $model
     *
     * @return null
     */
    protected function stopIfHasParts($model)
    {

        //If status is cancelled for tasks
        if ($model->isDirty('status_id') && $model->status_id == Status::TASKS_CANCELLED && $model->parts->count() > 0) {
            throw new Exception('To cancel the task, you first have to delete the parts and put the stock back.', 1);
        }
    }

    /**
     * Log changes to task fields.
     *
     * @param  App\Modules\Bookings\Task $model
     *
     * @return null
     */
    protected function logFieldChanges($model)
    {
        try {
            $userId = getAuthUserId();

            // Define trackable fields
            $trackableFields = [
                'title', 'problem', 'assigned_to', 'severity_id', 'type', 'subtype_id', 'return_id',
                'standard_price_id', 'labour', 'needs_delivery', 'delivered_by', 'delivered', 'needs_pickup',
                'pickup_by', 'pickup_schedule', 'picked_up_at', 'pin', 'shelf', 'shelf_type',
                'prebook', 'prebook_date', 'onsite_start', 'onsite_end', 'time_spent',
                'attended_time', 'unattended_time', 'alarm_date', 'due_date', 'passed_qa',
                'satisfied_client', 'needs_calling', 'cancelled_reason', 'antivirus_state',
                'no_price_reason', 'nopassword_reason', 'direct_dial',
            ];

            // Get all dirty fields
            $dirtyFields = $model->getDirty();

            foreach ($dirtyFields as $field => $newValue) {
                // Skip fields we handle elsewhere (status_id and assigned_to have their own tracking)
                if (in_array($field, ['status_id', 'assigned_to', 'completed', 'completed_at', 'updated_at', 'created_at'])) {
                    continue;
                }

                // Skip if field not in our trackable list
                if (!in_array($field, $trackableFields)) {
                    continue;
                }

                $oldValue = $model->getOriginal($field);

                // Format the old and new values for storage
                $formattedOld = $this->formatFieldValue($field, $oldValue, $model);
                $formattedNew = $this->formatFieldValue($field, $newValue, $model);

                // Skip if values are the same after formatting
                if ($formattedOld === $formattedNew) {
                    continue;
                }

                // Create field change record
                \App\Models\FieldChange::create([
                    'related_id' => $model->id,
                    'related_type' => 'task',
                    'field_name' => $field,
                    'old_value' => $formattedOld,
                    'new_value' => $formattedNew,
                    'changed_by' => $userId,
                ]);
            }
        } catch (Exception $e) {
            Log::error('Error logging field changes: ' . $e->getMessage());
        }
    }

    /**
     * Format field values for display in change logs.
     *
     * @param  string $field
     * @param  mixed $value
     * @param  App\Modules\Bookings\Task $model
     *
     * @return string
     */
    protected function formatFieldValue($field, $value, $model)
    {
        // Handle null/empty values
        if (is_null($value) || $value === '') {
            return 'Not set';
        }

        // Handle boolean fields
        if (in_array($field, ['needs_delivery', 'delivered', 'needs_pickup', 'prebook', 'passed_qa', 'satisfied_client', 'needs_calling'])) {
            return $value ? 'Yes' : 'No';
        }

        // Handle user fields
        if (in_array($field, ['assigned_to', 'delivered_by', 'pickup_by'])) {
            $user = User::find($value);
            return $user ? $user->name : 'Not assigned';
        }

        // Handle severity
        if ($field === 'severity_id') {
            $severity = Severity::find($value);
            return $severity ? $severity->name : 'Not set';
        }

        // Handle subtype
        if ($field === 'subtype_id') {
            $subtype = Subtype::find($value);
            return $subtype ? $subtype->title : 'Not set';
        }

        // Handle standard_price_id
        if ($field === 'standard_price_id') {
            $standardPrice = \App\Models\StandardPrice::find($value);
            return $standardPrice ? $standardPrice->name : 'Not set';
        }

        // Handle shelf type
        if ($field === 'shelf_type') {
            $shelfTypes = Task::getShelfTypes();
            return isset($shelfTypes[$value]) ? $shelfTypes[$value] : $value;
        }

        // Handle return_id (old job reference)
        if ($field === 'return_id') {
            if ($value) {
                return "Task #{$value}";
            }
            return 'Not set';
        }

        // Handle type (repair type)
        if ($field === 'type') {
            $types = [
                'repair' => 'Repair',
                'support' => 'Support',
                'onsite' => 'Onsite',
                'followup' => 'Follow-up',
                'qa' => 'QA',
                'order' => 'Order',
                'rma' => 'RMA',
                'sale_lead' => 'Sale Lead',
            ];
            return isset($types[$value]) ? $types[$value] : ucfirst($value);
        }

        // Handle antivirus_state
        if ($field === 'antivirus_state') {
            $antivirusStates = Task::getAntivirusStates();
            return isset($antivirusStates[$value]) ? $antivirusStates[$value] : 'Not set';
        }

        // Handle datetime fields
        if (in_array($field, ['pickup_schedule', 'prebook_date', 'onsite_start', 'onsite_end', 'alarm_date', 'due_date', 'picked_up_at'])) {
            return date('d/m/Y H:i', strtotime($value));
        }

        // Handle money fields
        if (in_array($field, ['labour'])) {
            return '£' . number_format($value, 2);
        }

        // Handle time fields (in minutes)
        if (in_array($field, ['time_spent', 'attended_time', 'unattended_time'])) {
            $hours = floor($value / 60);
            $mins = $value % 60;
            return "{$hours}h {$mins}m";
        }

        // Default: return as string
        return (string) $value;
    }
}
