<?php

namespace App\Components\Dashboard;

use App\Models\Client\Client;
use App\Models\Client\ClientHandler;
use App\Models\Communication\Communication;
use App\Models\Contact\Hub\ContactMessage;
use App\Models\ContractRequest;
use App\Models\Document\Document;
use App\Models\Solicitor\Solicitor;
use App\Models\User\User;
use App\Models\WipEntry;
use App\Support\LeadContactDeadline;
use Carbon\Carbon;
use Illuminate\Support\Collection;
use Livewire\Component;

class ClaimsHandlerDashboard extends Component
{
    public int $handlerId;
    public string $reportMonth;
    public ?string $activeMetric = null;
    public string $activePeriod = 'all';

    public function mount(?User $user = null): void
    {
        $this->handlerId = ($user ?? auth()->user())->id;
        $this->reportMonth = now()->format('Y-m');
    }

    public function updatedHandlerId(): void
    {
        $this->activeMetric = null;
        $this->activePeriod = 'all';
    }

    public function updatedReportMonth(): void
    {
        if (!preg_match('/^\d{4}-(0[1-9]|1[0-2])$/', $this->reportMonth)) {
            $this->reportMonth = now()->format('Y-m');
        }

        $this->activeMetric = null;
        $this->activePeriod = 'all';
    }

    public function showMetric(string $metric, string $period = 'all'): void
    {
        abort_unless(in_array($metric, [
            'caseload',
            'documents_sent',
            'documents_signed',
            'submitted_redress',
            'documents_outstanding',
            'new_leads',
            'contact_breaches',
        ], true), 404);
        abort_unless(in_array($period, ['all', 'today', 'week', 'month'], true), 404);

        $this->activeMetric = $metric;
        $this->activePeriod = $period;
    }

    public function closeMetric(): void
    {
        $this->activeMetric = null;
        $this->activePeriod = 'all';
    }

    public function render()
    {
        $handler = User::findOrFail($this->handlerId);

        abort_unless(
            $handler->is(auth()->user()) || auth()->user()->can('users.view'),
            403
        );

        $periods = $this->periods();
        $workspaceId = app(Solicitor::class)?->id;
        $assignedClients = Client::query()
            ->whereHas('handlers', fn ($query) => $query->where('user_id', $handler->id))
            ->with(['claim.status', 'handlers', 'documents.type', 'claim.documents.type'])
            ->get()
            ->unique('id')
            ->values();

        $assignedClientIds = $assignedClients->pluck('id');
        $caseload = $assignedClients
            ->filter(fn (Client $client) => $client->claim
                && strcasecmp((string) $client->claim->status?->display_name, 'Closed') !== 0)
            ->values();

        [$documentsSent, $documentsSigned] = $this->esignMetrics($handler, $assignedClientIds, $periods);
        $submittedRedress = $this->submittedRedressMetrics($handler, $assignedClientIds, $periods);
        $newLeads = $this->newLeadMetrics($handler, $periods, $workspaceId);
        $contactBreaches = $this->contactBreachMetrics($handler, $newLeads['records'], $periods);
        $documentsOutstanding = $this->documentsOutstanding($assignedClients);

        $wipUnits = (int) WipEntry::query()
            ->where('user_id', $handler->id)
            ->when($workspaceId, fn ($query) => $query->where('solicitor_id', $workspaceId))
            ->whereBetween('created_at', $periods['today'])
            ->sum('units');

        $metricLists = [
            'caseload' => [
                'title' => 'Caseload',
                'subtitle' => 'Active files currently assigned',
                'items' => $this->clientRows($caseload),
            ],
            'documents_sent' => [
                'title' => 'Documents Sent',
                'subtitle' => 'Sent and still awaiting signature',
                'items' => $this->clientRows($documentsSent['records']),
                'period_items' => $documentsSent['period_records'],
            ],
            'documents_signed' => [
                'title' => 'Documents Signed & Received',
                'subtitle' => 'Completed e-sign paperwork',
                'items' => $this->clientRows($documentsSigned['records']),
                'period_items' => $documentsSigned['period_records'],
            ],
            'submitted_redress' => [
                'title' => 'Submitted to Redress',
                'subtitle' => 'Applications sent to the Scheme',
                'items' => $this->clientRows($submittedRedress['records']),
                'period_items' => $submittedRedress['period_records'],
            ],
            'documents_outstanding' => [
                'title' => 'POA / POI Outstanding',
                'subtitle' => 'Assigned files missing identity paperwork',
                'items' => $this->clientRows($documentsOutstanding),
            ],
            'new_leads' => [
                'title' => 'New Leads',
                'subtitle' => 'Files assigned to this handler',
                'items' => $this->clientRows($newLeads['records']->pluck('client')->filter()->unique('id')->values()),
                'period_items' => $newLeads['period_records'],
            ],
            'contact_breaches' => [
                'title' => '15-Minute Contact Breaches',
                'subtitle' => 'Leads without a phone call by the applicable deadline',
                'items' => $this->clientRows($contactBreaches['records']->pluck('client')->filter()->unique('id')->values()),
                'period_items' => $contactBreaches['period_records'],
                'default_period_label' => Carbon::createFromFormat('Y-m', $this->reportMonth)->format('M Y'),
            ],
        ];

        $cards = [
            [
                'key' => 'caseload',
                'title' => 'Caseload',
                'value' => $caseload->count(),
                'description' => 'Active files currently assigned',
                'icon' => 'mdi-briefcase-outline',
                'tone' => 'indigo',
            ],
            [
                'key' => 'documents_sent',
                'title' => 'Documents Sent',
                'value' => $documentsSent['records']->count(),
                'description' => 'Sent but not yet signed',
                'icon' => 'mdi-file-send-outline',
                'tone' => 'orange',
                'periods' => $documentsSent['counts'],
            ],
            [
                'key' => 'documents_signed',
                'title' => 'Documents Signed & Received',
                'value' => $documentsSigned['records']->count(),
                'description' => 'Completed client paperwork',
                'icon' => 'mdi-file-check-outline',
                'tone' => 'green',
                'periods' => $documentsSigned['counts'],
            ],
            [
                'key' => 'wip',
                'title' => 'WIP Units',
                'value' => $wipUnits,
                'description' => 'Daily target progress',
                'icon' => 'mdi-chart-bar',
                'tone' => 'blue',
                'progress' => [
                    'value' => $wipUnits,
                    'max' => max(140, $wipUnits),
                ],
            ],
            [
                'key' => 'submitted_redress',
                'title' => 'Submitted to Redress',
                'value' => $submittedRedress['records']->count(),
                'description' => 'Applications sent onward',
                'icon' => 'mdi-file-upload-outline',
                'tone' => 'blue',
                'periods' => $submittedRedress['counts'],
            ],
            [
                'key' => 'documents_outstanding',
                'title' => 'POA / POI Outstanding',
                'value' => $documentsOutstanding->count(),
                'description' => 'Waiting on proof of address / identity',
                'icon' => 'mdi-card-account-details-outline',
                'tone' => 'red',
            ],
            [
                'key' => 'new_leads',
                'title' => 'New Leads',
                'description' => 'Files assigned to this handler',
                'icon' => 'mdi-account-plus-outline',
                'tone' => 'purple',
                'periods' => $newLeads['counts'],
            ],
            [
                'key' => 'contact_breaches',
                'title' => '15-Minute Contact Breaches',
                'value' => $contactBreaches['counts']['month'],
                'description' => 'Leads that missed the first-call deadline',
                'icon' => 'mdi-phone-alert-outline',
                'tone' => 'red',
                'periods' => $contactBreaches['counts'],
            ],
        ];

        $leadQueue = Client::query()
            ->whereNotNull('lead_source_endpoint_id')
            ->doesntHave('handlers')
            ->with(['claim.status', 'leadSource'])
            ->latest()
            ->get();

        $handlerOptions = auth()->user()->can('users.view')
            ? User::query()
                ->with('solicitor')
                ->orderBy('name_first')
                ->orderBy('name_last')
                ->get()
            : collect([$handler]);

        $activeList = $this->activeMetric ? ($metricLists[$this->activeMetric] ?? null) : null;
        if ($activeList && $this->activePeriod !== 'all' && isset($activeList['period_items'])) {
            $activeList['items'] = $activeList['period_items'][$this->activePeriod] ?? collect();
        }

        $activePeriodLabel = match ($this->activePeriod) {
            'today' => 'Today',
            'week' => 'This Week',
            'month' => Carbon::createFromFormat('Y-m', $this->reportMonth)->format('M Y'),
            default => $activeList['default_period_label'] ?? 'All files',
        };

        return view('components.dashboard.claims-handler-dashboard', [
            'handler' => $handler,
            'handlerOptions' => $handlerOptions,
            'cards' => $cards,
            'leadQueue' => $leadQueue,
            'activeList' => $activeList,
            'activePeriodLabel' => $activePeriodLabel,
            'monthLabel' => Carbon::createFromFormat('Y-m', $this->reportMonth)->format('M Y'),
        ])->layout('layouts.dashboard');
    }

    private function periods(): array
    {
        $now = now();
        $weekStart = $now->copy()->startOfWeek(Carbon::MONDAY)->startOfDay();
        $weekEnd = $now->copy()->startOfWeek(Carbon::MONDAY)->addDays(4)->endOfDay();
        $selectedMonth = Carbon::createFromFormat('Y-m', $this->reportMonth)->startOfMonth();

        return [
            'today' => [$now->copy()->startOfDay(), $now->copy()->endOfDay()],
            'week' => [$weekStart, $now->copy()->min($weekEnd)],
            'month' => [$selectedMonth, $selectedMonth->copy()->endOfMonth()],
        ];
    }

    private function esignMetrics(User $handler, Collection $assignedClientIds, array $periods): array
    {
        $requests = ContractRequest::query()
            ->where('user_id', $handler->id)
            ->whereIn('client_id', $assignedClientIds)
            ->with(['client.claim.status', 'esignDocuments'])
            ->get()
            ->map(function (ContractRequest $request) {
                $sentDocuments = $request->esignDocuments->filter(fn ($document) => $document->sent_at);
                if ($sentDocuments->isEmpty()) {
                    return null;
                }

                $isComplete = $sentDocuments->every(fn ($document) => $document->signed_at || $document->completed_at);
                $request->dashboard_sent_at = $sentDocuments->min('sent_at');
                $request->dashboard_completed_at = $isComplete
                    ? $sentDocuments->max(fn ($document) => $document->completed_at ?? $document->signed_at)
                    : null;

                return $request;
            })
            ->filter();

        $outstanding = $requests
            ->filter(fn ($request) => $request->dashboard_completed_at === null)
            ->unique('client_id')
            ->values();

        $completedRequests = $requests
            ->filter(fn ($request) => $request->dashboard_completed_at !== null)
            ->sortByDesc('dashboard_completed_at');
        $signed = $completedRequests
            ->unique('client_id')
            ->values();

        $outstandingCounts = [];
        $outstandingPeriodRecords = [];
        foreach ($periods as $key => [, $to]) {
            // Show the unsigned position at the end of each reporting period.
            // For the current/future period, "now" is the cutoff. This carries
            // older unsigned packs forward while removing a pack immediately
            // after it is completed.
            $cutoff = $to->isFuture() ? now() : $to;
            $periodRequests = $requests
                ->filter(fn ($request) => $request->dashboard_sent_at <= $cutoff
                    && ($request->dashboard_completed_at === null || $request->dashboard_completed_at > $cutoff))
                ->unique('client_id')
                ->values();
            $outstandingCounts[$key] = $periodRequests->count();
            $outstandingPeriodRecords[$key] = $this->clientRows(
                $periodRequests->pluck('client')->filter()->values()
            );
        }

        return [
            [
                'records' => $outstanding->pluck('client')->filter()->values(),
                'counts' => $outstandingCounts,
                'period_records' => $outstandingPeriodRecords,
            ],
            [
                'records' => $signed->pluck('client')->filter()->values(),
                'counts' => $this->distinctPeriodCounts(
                    $completedRequests,
                    $periods,
                    fn ($request) => $request->dashboard_completed_at,
                    fn ($request) => $request->client_id,
                ),
                'period_records' => $this->clientRowsByPeriod(
                    $completedRequests,
                    $periods,
                    fn ($request) => $request->dashboard_completed_at,
                    fn ($request) => $request->client,
                ),
            ],
        ];
    }

    private function submittedRedressMetrics(User $handler, Collection $assignedClientIds, array $periods): array
    {
        $messages = ContactMessage::query()
            ->where('created_by', $handler->id)
            ->whereNotNull('sent_at')
            ->whereHas('template', fn ($query) => $query
                ->where('key', 'scheme_enc_application')
                ->orWhereIn('name', [
                    'Enc Application',
                    'Email to Scheme - Intake - Enc Application',
                ]))
            ->with(['messageable', 'related', 'template'])
            ->get()
            ->map(function (ContactMessage $message) {
                $client = $this->messageClient($message);
                if (!$client) {
                    return null;
                }

                $message->dashboard_client = $client;
                $message->dashboard_at = $message->sent_at ?? $message->created_at;

                return $message;
            })
            ->filter(fn ($message) => $message && $assignedClientIds->contains($message->dashboard_client->id))
            ->sortByDesc('dashboard_at');
        $records = $messages
            ->unique(fn ($message) => $message->dashboard_client->id)
            ->values();

        return [
            'records' => $records->map(fn ($message) => $message->dashboard_client)->values(),
            'counts' => $this->distinctPeriodCounts(
                $messages,
                $periods,
                fn ($message) => $message->dashboard_at,
                fn ($message) => $message->dashboard_client->id,
            ),
            'period_records' => $this->clientRowsByPeriod(
                $messages,
                $periods,
                fn ($message) => $message->dashboard_at,
                fn ($message) => $message->dashboard_client,
            ),
        ];
    }

    private function newLeadMetrics(User $handler, array $periods, ?int $workspaceId): array
    {
        $records = ClientHandler::query()
            ->where('user_id', $handler->id)
            ->when($workspaceId, fn ($query) => $query->whereHas(
                'client',
                fn ($clientQuery) => $clientQuery->where('solicitor_id', $workspaceId)
            ))
            ->with(['client.claim.status'])
            ->latest()
            ->get()
            ->unique('client_id')
            ->values();

        return [
            'records' => $records,
            'counts' => $this->periodCounts($records, $periods, fn ($assignment) => $assignment->created_at),
            'period_records' => $this->clientRowsByPeriod(
                $records,
                $periods,
                fn ($assignment) => $assignment->created_at,
                fn ($assignment) => $assignment->client,
            ),
        ];
    }

    private function contactBreachMetrics(User $handler, Collection $assignments, array $periods): array
    {
        $clientIds = $assignments->pluck('client_id');
        $claimIds = $assignments->pluck('client.claim.id')->filter();

        $calls = Communication::query()
            ->where('agent_id', $handler->id)
            ->whereIn('direction', ['inbound', 'outbound'])
            ->where('channel', 'voice')
            ->where(function ($query) use ($clientIds, $claimIds) {
                $query->where(function ($query) use ($clientIds) {
                    $query->where('communicable_type', Client::class)
                        ->whereIn('communicable_id', $clientIds);
                })->orWhereIn('claim_id', $claimIds);
            })
            ->with('claim')
            ->get()
            ->groupBy(function (Communication $communication) {
                if ($communication->communicable_type === Client::class) {
                    return $communication->communicable_id;
                }

                return $communication->claim?->client_id;
            });

        $allBreaches = $assignments->filter(function (ClientHandler $assignment) use ($calls) {
            $leadCreatedAt = $assignment->client?->created_at ?? $assignment->created_at;
            $contactDeadline = LeadContactDeadline::for($leadCreatedAt);
            $firstAttempt = $calls->get($assignment->client_id)
                ?->filter(fn (Communication $call) => ($call->started_at ?? $call->created_at)->gte($leadCreatedAt))
                ->min(fn (Communication $call) => $call->started_at ?? $call->created_at);

            return (!$firstAttempt && $contactDeadline->isPast())
                || ($firstAttempt && $firstAttempt->gt($contactDeadline));
        })->values();

        [$monthStart, $monthEnd] = $periods['month'];
        $records = $allBreaches->filter(function (ClientHandler $assignment) use ($monthStart, $monthEnd) {
            $leadCreatedAt = $assignment->client?->created_at ?? $assignment->created_at;

            return $leadCreatedAt->betweenIncluded($monthStart, $monthEnd);
        })->values();

        return [
            'records' => $records,
            'counts' => $this->periodCounts(
                $allBreaches,
                $periods,
                fn ($assignment) => $assignment->client?->created_at ?? $assignment->created_at
            ),
            'period_records' => $this->clientRowsByPeriod(
                $allBreaches,
                $periods,
                fn ($assignment) => $assignment->client?->created_at ?? $assignment->created_at,
                fn ($assignment) => $assignment->client,
            ),
        ];
    }

    private function documentsOutstanding(Collection $clients): Collection
    {
        return $clients->filter(function (Client $client) {
            $status = trim((string) $client->claim?->status?->display_name);
            if (!in_array($status, [
                'Part 1 & 3 Sent To Redress',
                'App Number Received',
            ], true)) {
                return false;
            }

            $documents = $client->documents
                ->concat($client->claim?->documents ?? collect());

            $poaCount = $documents
                ->filter(fn (Document $document) => strcasecmp((string) $document->type?->display_name, 'POA') === 0)
                ->count();
            $hasProofOfIdentity = $documents->contains(fn (Document $document) => in_array(
                strtolower((string) $document->type?->display_name),
                ['id', 'poi', 'proof of identity'],
                true
            ));

            return !(($poaCount >= 1 && $hasProofOfIdentity) || $poaCount >= 3);
        })->values();
    }

    private function periodCounts(Collection $records, array $periods, callable $timestamp): array
    {
        return collect($periods)->mapWithKeys(function ($range, $key) use ($records, $timestamp) {
            [$from, $to] = $range;

            return [$key => $records->filter(function ($record) use ($timestamp, $from, $to) {
                $at = $timestamp($record);
                return $at && $at->betweenIncluded($from, $to);
            })->count()];
        })->all();
    }

    private function distinctPeriodCounts(
        Collection $records,
        array $periods,
        callable $timestamp,
        callable $distinctKey,
    ): array {
        return collect($periods)->mapWithKeys(function ($range, $key) use ($records, $timestamp, $distinctKey) {
            [$from, $to] = $range;

            return [$key => $records
                ->filter(function ($record) use ($timestamp, $from, $to) {
                    $at = $timestamp($record);
                    return $at && $at->betweenIncluded($from, $to);
                })
                ->unique($distinctKey)
                ->count()];
        })->all();
    }

    private function clientRowsByPeriod(
        Collection $records,
        array $periods,
        callable $timestamp,
        callable $client,
    ): array {
        return collect($periods)->mapWithKeys(function ($range, $key) use ($records, $timestamp, $client) {
            [$from, $to] = $range;
            $clients = $records
                ->filter(function ($record) use ($timestamp, $from, $to) {
                    $at = $timestamp($record);
                    return $at && $at->betweenIncluded($from, $to);
                })
                ->map($client)
                ->filter()
                ->unique('id')
                ->values();

            return [$key => $this->clientRows($clients)];
        })->all();
    }

    private function messageClient(ContactMessage $message): ?Client
    {
        foreach ([$message->messageable, $message->related] as $target) {
            if ($target instanceof Client) {
                return $target;
            }

            if ($target instanceof \App\Models\Claim\Claim) {
                return $target->client;
            }
        }

        return null;
    }

    private function clientRows(Collection $clients): Collection
    {
        return $clients->filter()->map(fn (Client $client) => [
            'id' => $client->id,
            'reference' => $client->reference ?: $client->display_id,
            'name' => trim(
                (string) $client->getRawOriginal('name_first') . ' ' .
                (string) $client->getRawOriginal('name_last')
            ),
            'status' => $client->claim?->status?->display_name ?? 'Lead',
            'created' => $client->created_at?->format('d M Y'),
            'url' => route('clients.show', $client),
        ])->values();
    }

}
