<?php

namespace App\Components\Report;

use App\Models\Claim\Claim;
use App\Models\Client\Client;
use App\Models\Solicitor\Solicitor;
use App\Models\Synapse\CallScore;
use App\Models\Tranche;
use App\Services\AppMilestoneService;
use App\Services\HistoryService;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;
use Livewire\Component;
use Livewire\WithPagination;

class ConversionReport extends Component
{
    use AuthorizesRequests, WithPagination;

    private const AUDIT_OVERRIDE_KEY = 'conversion_report.initial_call_audit_passed';

    public string $search = '';
    public string $trancheId = '';
    public int $perPage = 25;
    public ?int $editingClientId = null;
    public string $editInitialAudit = '';
    public string $editAppNumberReceivedDate = '';
    public ?int $savedClientId = null;

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

    public function updatingSearch(): void
    {
        $this->resetPage();
    }

    public function updatingTrancheId(): void
    {
        $this->resetPage();
    }

    public function updatingPerPage(): void
    {
        $this->resetPage();
    }

    public function edit(int $clientId): void
    {
        $this->authorize('reports.conversions.edit');
        $client = $this->convertedClient($clientId);
        $auditOverride = $client->getMeta(self::AUDIT_OVERRIDE_KEY);

        $this->editingClientId = $clientId;
        $this->editInitialAudit = match ($auditOverride) {
            true => 'pass',
            false => 'fail',
            default => '',
        };
        $this->editAppNumberReceivedDate = $client->claim?->app_receival_date
            ? $client->claim->app_receival_date->format('Y-m-d')
            : '';
        $this->savedClientId = null;
        $this->resetValidation();
    }

    public function cancelEdit(): void
    {
        $this->resetEditState();
    }

    public function saveCorrections(HistoryService $history, AppMilestoneService $milestones): void
    {
        $this->authorize('reports.conversions.edit');

        $data = $this->validate([
            'editingClientId' => ['required', 'integer'],
            'editInitialAudit' => ['nullable', 'in:pass,fail'],
            'editAppNumberReceivedDate' => ['nullable', 'date'],
        ]);

        $client = $this->convertedClient((int) $data['editingClientId']);
        $audit = match ($data['editInitialAudit']) {
            'pass' => true,
            'fail' => false,
            default => null,
        };
        $appDate = filled($data['editAppNumberReceivedDate'])
            ? $data['editAppNumberReceivedDate']
            : null;

        $before = [
            'initial_call_audit_passed' => $client->getMeta(self::AUDIT_OVERRIDE_KEY),
            'app_receival_date' => $client->claim?->app_receival_date?->toDateString(),
        ];

        if ($audit === null) {
            $client->deleteMeta(self::AUDIT_OVERRIDE_KEY);
        } else {
            $client->setMeta(self::AUDIT_OVERRIDE_KEY, $audit);
        }

        if ($client->claim) {
            $dateChanged = $client->claim->app_receival_date?->toDateString() !== $appDate;
            $client->claim->update(['app_receival_date' => $appDate]);

            if ($dateChanged) {
                $milestones->recordReceived($client->claim);
            }
        }

        $history->add($client, 'Conversion report corrections updated', [
            'before' => $before,
            'initial_call_audit_passed' => $audit,
            'app_receival_date' => $appDate,
        ]);

        $this->savedClientId = $client->id;
        $this->resetEditState(false);
    }

    public function render()
    {
        $clients = $this->conversionQuery()
            ->with([
                'tranche',
                'metas' => fn ($query) => $query->whereIn('key', [
                    self::AUDIT_OVERRIDE_KEY,
                ]),
                'documents.type',
                'claim.documents.type',
            ])
            ->when($this->search, function (Builder $query, string $search) {
                $displayId = preg_match('/^1(\d{5,})$/', trim($search), $matches)
                    ? (int) $matches[1]
                    : null;

                $query->where(function (Builder $query) use ($search, $displayId) {
                    $query->where('clients.name_first', 'like', "%{$search}%")
                        ->orWhere('clients.name_middle', 'like', "%{$search}%")
                        ->orWhere('clients.name_last', 'like', "%{$search}%")
                        ->orWhereHas('claim', fn (Builder $claim) => $claim->where('case_number', 'like', "%{$search}%"));

                    if ($displayId !== null) {
                        $query->orWhere('clients.id', $displayId);
                    }
                });
            })
            ->when($this->trancheId, fn (Builder $query, string $trancheId) => $query->where('clients.tranche_id', $trancheId))
            ->orderByDesc('conversion_summary.conversion_date')
            ->orderByDesc('clients.id')
            ->paginate($this->perPage);

        return view('components.report.conversion-report', [
            'clients' => $clients,
            'rows' => $this->buildRows(collect($clients->items())),
            'tranches' => Tranche::query()
                ->where('solicitor_id', $this->solicitorId())
                ->orderBy('tranche_name')
                ->get(['id', 'tranche_name']),
        ]);
    }

    private function conversionQuery(): Builder
    {
        $successfulRequests = DB::table('esign_documents')
            ->selectRaw('contract_request_id, MAX(COALESCE(completed_at, signed_at)) AS converted_at')
            ->whereNotNull('sent_at')
            ->groupBy('contract_request_id')
            ->havingRaw('COUNT(*) = SUM(CASE WHEN signed_at IS NOT NULL OR completed_at IS NOT NULL THEN 1 ELSE 0 END)');

        $conversionSummary = DB::table('contract_requests')
            ->joinSub($successfulRequests, 'successful_requests', function ($join) {
                $join->on('successful_requests.contract_request_id', '=', 'contract_requests.id');
            })
            ->selectRaw('contract_requests.client_id, MIN(successful_requests.converted_at) AS conversion_date')
            ->groupBy('contract_requests.client_id');

        return Client::query()
            ->joinSub($conversionSummary, 'conversion_summary', function ($join) {
                $join->on('conversion_summary.client_id', '=', 'clients.id');
            })
            ->where('clients.solicitor_id', $this->solicitorId())
            ->select(['clients.*', 'conversion_summary.conversion_date']);
    }

    private function buildRows(Collection $clients): Collection
    {
        if ($clients->isEmpty()) {
            return collect();
        }

        $clientIds = $clients->pluck('id')->map(fn ($id) => (int) $id)->values();
        $claimToClient = $clients->pluck('id', 'claim.id')
            ->filter(fn ($clientId, $claimId) => filled($claimId))
            ->mapWithKeys(fn ($clientId, $claimId) => [(int) $claimId => (int) $clientId]);
        $claimIds = $claimToClient->keys()->values();

        $auditResults = $this->auditResults($clientIds, $claimIds, $claimToClient);

        return $clients->map(function (Client $client) use ($auditResults) {
            $conversionDate = Carbon::parse($client->getAttribute('conversion_date'));
            $overrides = $client->metas->pluck('value', 'key');
            $auditOverride = $overrides->get(self::AUDIT_OVERRIDE_KEY);
            $automaticAudit = $auditResults->get($client->id);
            $identity = $this->identityStatus($client);
            $sentDate = $client->claim?->app_sent_date;
            $estimatedDate = $client->claim?->app_est_receival_date;

            return [
                'client' => $client,
                'reference' => $client->display_id,
                'name' => $client->full_name,
                'conversion_date' => $conversionDate,
                'cancellation_expiry' => $conversionDate->copy()->addDays(14),
                'redress_date' => $sentDate,
                'estimated_app_date' => $estimatedDate,
                'estimated_app_days' => $sentDate && $estimatedDate
                    ? (int) round($sentDate->diffInDays($estimatedDate))
                    : null,
                'audit_passed' => $overrides->has(self::AUDIT_OVERRIDE_KEY) ? (bool) $auditOverride : $automaticAudit,
                'audit_is_manual' => $overrides->has(self::AUDIT_OVERRIDE_KEY),
                'app_number' => $client->claim?->case_number,
                'app_number_received_at' => $client->claim?->app_receival_date,
                'identity_received' => $identity['received'],
                'identity_received_at' => $identity['received_at'],
                'tranche' => $client->tranche?->tranche_name,
            ];
        });
    }

    private function auditResults(Collection $clientIds, Collection $claimIds, Collection $claimToClient): Collection
    {
        $scores = CallScore::query()
            ->where('call_scores.solicitor_id', $this->solicitorId())
            ->where('is_initial_call', true)
            ->where('status', 'completed')
            ->whereHas('communication', function (Builder $query) use ($clientIds, $claimIds) {
                $query->whereIn('claim_id', $claimIds)
                    ->orWhere(function (Builder $query) use ($clientIds) {
                        $query->where('communicable_type', Client::class)
                            ->whereIn('communicable_id', $clientIds);
                    })->orWhere(function (Builder $query) use ($claimIds) {
                        $query->where('communicable_type', Claim::class)
                            ->whereIn('communicable_id', $claimIds);
                    });
            })
            ->with('communication:id,claim_id,communicable_type,communicable_id')
            ->get();

        return $scores
            ->groupBy(function (CallScore $score) use ($claimToClient) {
                $communication = $score->communication;
                if ($communication?->communicable_type === Client::class) {
                    return (int) $communication->communicable_id;
                }

                $claimId = $communication?->claim_id
                    ?? ($communication?->communicable_type === Claim::class ? $communication->communicable_id : null);

                return $claimId ? $claimToClient->get((int) $claimId) : null;
            })
            ->filter(fn (Collection $group, $clientId) => filled($clientId))
            ->map(function (Collection $clientScores) {
                if ($clientScores->contains(fn (CallScore $score) => $score->passed === false)) {
                    return false;
                }

                return $clientScores->contains(fn (CallScore $score) => $score->passed === true) ? true : null;
            });
    }

    private function identityStatus(Client $client): array
    {
        $poa = 0;
        $poi = 0;
        $receivedAt = null;
        $documents = $client->documents
            ->concat($client->claim?->documents ?? collect())
            ->sortBy('created_at');

        foreach ($documents as $document) {
            $type = strtolower(trim((string) $document->type?->display_name));
            if ($type === 'poa' || $type === 'proof of address') {
                $poa++;
            }
            if (in_array($type, ['id', 'poi', 'proof of identity'], true)) {
                $poi++;
            }

            if (($poa >= 1 && $poi >= 1) || $poa >= 3) {
                $receivedAt = $document->created_at;
                break;
            }
        }

        return [
            'received' => $receivedAt !== null,
            'received_at' => $receivedAt,
        ];
    }

    private function convertedClient(int $clientId): Client
    {
        return $this->conversionQuery()->where('clients.id', $clientId)->firstOrFail();
    }

    private function solicitorId(): int
    {
        return (int) app(Solicitor::class)->getKey();
    }

    private function resetEditState(bool $clearSaved = true): void
    {
        $this->editingClientId = null;
        $this->editInitialAudit = '';
        $this->editAppNumberReceivedDate = '';
        if ($clearSaved) {
            $this->savedClientId = null;
        }
        $this->resetValidation();
    }
}
