<?php

namespace App\Components\Contact;

use App\Enum\Dsar\DsarStatus;
use App\Models\Client\ClientHandler;
use App\Models\Contact\Hub\ContactMessage;
use App\Models\Dsar\Dsar;
use App\Models\Solicitor\Solicitor;
use App\Services\HistoryService;
use App\Trait\LogsInboxActivity;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Livewire\Attributes\Url;
use Livewire\Component;

class InboxTriage extends Component
{
    use AuthorizesRequests, LogsInboxActivity;

    #[Url]
    public string $tab = 'dsar_evidence';

    // 'mine' | 'mine_and_unmatched' | 'unmatched_only'
    public string $viewMode = 'mine';

    public ?int $overrideHandlerId = null;

    public ?int $currentMessageId = null;

    public string $dsarSearch = '';

    public bool $relinking = false;

    // ── Lifecycle ─────────────────────────────────────────────────────────────

    public function mount(): void
    {
        $this->authorize('inbox.view');
        $this->loadNextEmail();
    }

    // ── Actions ───────────────────────────────────────────────────────────────

    public function setTab(string $tab): void
    {
        $this->tab = $tab;
        $this->loadNextEmail();
    }

    public function setViewMode(string $mode): void
    {
        $this->viewMode = $mode;
        $this->loadNextEmail();
    }

    public function setOverrideHandler(mixed $userId): void
    {
        $this->authorize('inbox.override_handler');
        $this->overrideHandlerId = $userId ? (int) $userId : null;
        $this->loadNextEmail();
    }

    public function dismiss(): void
    {
        if (! $this->currentMessageId) {
            return;
        }

        $this->authorizeMessage($this->currentMessageId);

        $message = ContactMessage::find($this->currentMessageId);

        ContactMessage::where('id', $this->currentMessageId)
            ->update(['triage_status' => 'actioned']);

        // Dismissing confirms the email belongs in the current tab → make the
        // linked DSAR's status match that tab.
        $change = $this->syncDsarStatusToQueue($this->tab);

        $action = 'Inbox: email dismissed from the ' . str_replace('_', ' ', $this->tab) . ' queue';
        if ($change) {
            $action .= " — DSAR status set to '{$change['new']}'";
        }
        $this->recordInboxAction($message, $action, array_merge(['queue' => $this->tab], $change ?? []));

        $this->loadNextEmail();
    }

    public function linkToDsar(int $dsarId): void
    {
        if (! $this->currentMessageId) {
            return;
        }

        $this->authorizeMessage($this->currentMessageId);

        $solicitorId = app(Solicitor::class)->id;

        $dsar = Dsar::withoutGlobalScopes()
            ->where('id', $dsarId)
            ->whereHas('claim', fn ($q) => $q->where('solicitor_id', $solicitorId))
            ->first();

        if (! $dsar) {
            return;
        }

        // Link the email to the DSAR but keep it in the triage list — DON'T mark
        // it actioned or advance. The triager still needs to Dismiss it, which is
        // what puts the DSAR into the correct status for its tab. Keep the email's
        // existing (classified) queue rather than forcing it to evidence.
        ContactMessage::where('id', $this->currentMessageId)->update([
            'messageable_type' => Dsar::class,
            'messageable_id'   => $dsar->id,
            'triage_status'    => 'in_progress',
        ]);

        $this->recordInboxAction(
            ContactMessage::find($this->currentMessageId),
            'Inbox: email linked to DSAR ' . ($dsar->reference ?: '#' . $dsar->id),
        );

        $this->dsarSearch = '';

        $this->dispatch('toast', message: 'Linked — dismiss to set the DSAR status.', type: 'success');
    }

    public function toggleRelink(): void
    {
        $this->relinking = ! $this->relinking;
        $this->dsarSearch = '';
    }

    public function relinkToDsar(int $dsarId): void
    {
        if (! $this->currentMessageId) {
            return;
        }

        $this->authorizeMessage($this->currentMessageId);

        $solicitorId = app(Solicitor::class)->id;

        $dsar = Dsar::withoutGlobalScopes()
            ->where('id', $dsarId)
            ->whereHas('claim', fn ($q) => $q->where('solicitor_id', $solicitorId))
            ->first();

        if (! $dsar) {
            return;
        }

        $previous       = ContactMessage::find($this->currentMessageId);
        $previousDsarId = $previous?->messageable_type === Dsar::class ? $previous->messageable_id : null;

        // Keep the email's existing (classified) queue — changing which DSAR it's
        // linked to doesn't change what kind of response it is.
        ContactMessage::where('id', $this->currentMessageId)->update([
            'messageable_type' => Dsar::class,
            'messageable_id'   => $dsar->id,
        ]);

        $newRef = $dsar->reference ?: '#' . $dsar->id;

        $this->recordInboxAction(
            ContactMessage::find($this->currentMessageId),
            'Inbox: email re-linked to DSAR ' . $newRef,
            array_filter(['previous_dsar_id' => $previousDsarId]),
        );

        // Leave a trace on the DSAR it was taken from, so its timeline isn't
        // silently stripped of the message.
        if ($previousDsarId && $previousDsarId !== $dsar->id) {
            $previousDsar = Dsar::withoutGlobalScopes()->find($previousDsarId);

            if ($previousDsar) {
                app(HistoryService::class)->add(
                    $previousDsar,
                    'Inbox: email re-linked away to DSAR ' . $newRef,
                    ['dsar_id' => $previousDsar->id, 'message_id' => $this->currentMessageId, 'new_dsar_id' => $dsar->id],
                );
            }
        }

        $this->relinking = false;
        $this->dsarSearch = '';
    }

    public function moveToQueue(string $queue): void
    {
        if (! $this->currentMessageId) {
            return;
        }

        $validQueues = ['dsar_evidence', 'dsar_updates', 'dsar_needs_research', 'portal_response', 'unknown'];

        if (! in_array($queue, $validQueues, true)) {
            return;
        }

        $this->authorizeMessage($this->currentMessageId);

        $message = ContactMessage::find($this->currentMessageId);

        ContactMessage::where('id', $this->currentMessageId)->update([
            'triage_queue'  => $queue,
            'triage_status' => 'pending',
        ]);

        // Moving an email into a tab sets the linked DSAR to that tab's status.
        $change = $this->syncDsarStatusToQueue($queue);

        $action = 'Inbox: email moved to the ' . str_replace('_', ' ', $queue) . ' queue';
        if ($change) {
            $action .= " — DSAR status set to '{$change['new']}'";
        }
        $this->recordInboxAction($message, $action, array_merge(['queue' => $queue], $change ?? []));

        $this->loadNextEmail();
    }

    /**
     * Manually set the status of the DSAR matched to the current email.
     * Stays on the current email (does not advance) so the triager can keep working it.
     */
    public function updateDsarStatus(string $status): void
    {
        if (! $this->currentMessageId) {
            return;
        }

        $this->authorize('dsars.batch.send');

        if (! in_array($status, DsarStatus::triageSelectable(), true)) {
            return;
        }

        $this->authorizeMessage($this->currentMessageId);

        $message = ContactMessage::find($this->currentMessageId);

        if (! $message || $message->messageable_type !== Dsar::class || ! $message->messageable_id) {
            return;
        }

        $solicitorId = app(Solicitor::class)->id;

        $dsar = Dsar::withoutGlobalScopes()
            ->where('id', $message->messageable_id)
            ->whereHas('claim', fn ($q) => $q->where('solicitor_id', $solicitorId))
            ->first();

        if (! $dsar) {
            return;
        }

        $previous = $dsar->status;
        $dsar->update(['status' => $status]);

        $this->recordInboxAction($message, "Inbox: DSAR status manually set to '{$status}'", [
            'previous_status' => $previous,
            'new_status'      => $status,
        ]);

        $this->dispatch('toast', message: 'DSAR status updated.', type: 'success');
    }

    // ── Render ────────────────────────────────────────────────────────────────

    public function render()
    {
        $solicitorId = app(Solicitor::class)->id;

        $counts = [];
        foreach (['dsar_evidence', 'dsar_updates', 'dsar_needs_research', 'portal_response', 'unknown'] as $queue) {
            $counts[$queue] = $this->buildHandlerQuery($solicitorId, $queue)->count();
        }

        $currentMessage = $this->currentMessageId
            ? ContactMessage::with(['attachments', 'messageable'])
                ->where('solicitor_id', $solicitorId)
                ->find($this->currentMessageId)
            : null;

        $thread = collect();
        if ($currentMessage?->messageable_type && $currentMessage->messageable instanceof Dsar) {
            $thread = ContactMessage::where('messageable_type', Dsar::class)
                ->where('messageable_id', $currentMessage->messageable_id)
                ->orderByDesc('created_at')
                ->get();
        }

        $dsarResults = collect();
        if ($currentMessage && ($this->relinking || ! $currentMessage->messageable_type) && strlen($this->dsarSearch) >= 2) {
            $dsarResults = Dsar::withoutGlobalScopes()
                ->where('status', '!=', 'draft')
                ->where(function ($q) {
                    $q->where('reference', 'like', "%{$this->dsarSearch}%")
                        ->orWhere('receiver_reference', 'like', "%{$this->dsarSearch}%")
                        ->orWhereHas('claim.client', fn ($q) =>
                            $q->where('name_first', 'like', "%{$this->dsarSearch}%")
                              ->orWhere('name_last', 'like', "%{$this->dsarSearch}%")
                        );
                })
                ->with(['claim.client', 'institution'])
                ->orderByDesc('created_at')
                ->get();
        }

        $availableHandlers = collect();
        if (auth()->user()->can('inbox.override_handler')) {
            $availableHandlers = ClientHandler::with('user')
                ->get()
                ->pluck('user')
                ->unique('id')
                ->filter()
                ->values();
        }

        return view('components.contact.inbox-triage', [
            'counts'            => $counts,
            'currentMessage'    => $currentMessage,
            'thread'            => $thread,
            'dsarResults'       => $dsarResults,
            'availableHandlers' => $availableHandlers,
        ]);
    }

    // ── Helpers ───────────────────────────────────────────────────────────────

    private function loadNextEmail(): void
    {
        $solicitorId = app(Solicitor::class)->id;
        $next = $this->buildHandlerQuery($solicitorId, $this->tab)->oldest()->first();

        $this->currentMessageId = $next?->id;
        $this->dsarSearch = '';
        $this->relinking = false;

        if ($next) {
            $opened = ContactMessage::where('id', $next->id)
                ->where('triage_status', 'pending')
                ->update(['triage_status' => 'in_progress', 'read_at' => now()]);

            // Only the first load flips an email pending → in_progress; log the
            // open once, not on every re-render that re-runs this method.
            if ($opened) {
                $this->recordInboxAction($next, 'Inbox: email opened for triage');
            }
        }
    }

    private function buildHandlerQuery($solicitorId, string $queue)
    {
        $effectiveUserId = $this->overrideHandlerId ?? auth()->id();

        $query = ContactMessage::inbound()
            ->where('solicitor_id', $solicitorId)
            ->where('triage_queue', $queue)
            ->whereIn('triage_status', ['pending', 'in_progress']);

        if ($this->viewMode === 'mine') {
            $query->whereNotNull('messageable_type')
                ->whereHasMorph('messageable', [Dsar::class], fn ($q) =>
                    $q->whereHas('claim.client.handlers', fn ($h) =>
                        $h->where('user_id', $effectiveUserId)
                    )
                );
        } elseif ($this->viewMode === 'mine_and_unmatched') {
            $query->where(fn ($q) =>
                $q->whereNull('messageable_type')
                    ->orWhere(fn ($q2) =>
                        $q2->whereNotNull('messageable_type')
                            ->whereHasMorph('messageable', [Dsar::class], fn ($mq) =>
                                $mq->whereHas('claim.client.handlers', fn ($h) =>
                                    $h->where('user_id', $effectiveUserId)
                                )
                            )
                    )
            );
        } else {
            // unmatched_only
            $query->whereNull('messageable_type');
        }

        return $query;
    }

    /**
     * Enforce the DSAR status implied by a triage queue when an email in it is
     * actioned (Dismiss enforces the current tab; "Move to…" sets the destination
     * tab). Only the two unambiguous queues map to a status; the rest are left
     * untouched so the AI's granular statuses (e.g. 'no records') are preserved.
     *
     * Guarded: needs the DSAR-status permission, only advances from a live,
     * non-terminal status, and is solicitor-scoped. Performs the change and
     * returns ['previous' => …, 'new' => …] so the caller can fold it into the
     * single history entry for the action; returns null when nothing changed.
     * Best-effort — never let a status hiccup break the triage action.
     */
    private function syncDsarStatusToQueue(string $queue): ?array
    {
        if (! $this->currentMessageId || ! auth()->user()->can('dsars.batch.send')) {
            return null;
        }

        $status = match ($queue) {
            'dsar_updates'  => DsarStatus::Acknowledged->value,
            'dsar_evidence' => DsarStatus::Completed->value,
            default         => null,
        };

        if (! $status) {
            return null;
        }

        try {
            $message = ContactMessage::find($this->currentMessageId);

            if (! $message || $message->messageable_type !== Dsar::class || ! $message->messageable_id) {
                return null;
            }

            $dsar = Dsar::withoutGlobalScopes()
                ->where('id', $message->messageable_id)
                ->whereHas('claim', fn ($q) => $q->where('solicitor_id', app(Solicitor::class)->id))
                ->whereIn('status', DsarStatus::autoFromStates())
                ->first();

            if (! $dsar || $dsar->status === $status) {
                return null;
            }

            $previous = $dsar->status;
            $dsar->update(['status' => $status]);

            return ['previous' => $previous, 'new' => $status];
        } catch (\Throwable $e) {
            report($e);
            return null;
        }
    }

    private function authorizeMessage(int $id): void
    {
        ContactMessage::where('id', $id)
            ->where('solicitor_id', app(Solicitor::class)->id)
            ->firstOrFail();
    }
}
