<?php

namespace App\Actions\Esign;

use App\Enum\Esign\EsignEventType;
use App\Enum\Esign\EsignStatus;
use App\Jobs\DownloadSignedEsignDocumentsJob;
use App\Models\ContractRequest;
use App\Models\EsignDocument;
use App\Models\EsignEvent;
use App\Services\ClientHandlerAlertService;
use App\Services\Esign\EsignEventMapper;
use App\Services\HistoryService;
use Carbon\Carbon;
use Illuminate\Support\Str;

/**
 * Ingest a single Fintrixor webhook event for an EsignDocument. Idempotent on
 * (esign_document_id, provider_event_id): records an audit row, advances the
 * document's status, recomputes the parent request aggregate, and fires the
 * one-time side-effects (download on document.completed; handler alert once the
 * whole request is completed).
 *
 * Runs in the unauthenticated webhook (console) context: reads withoutGlobalScopes,
 * stamps solicitor_id explicitly, never relies on a bound Solicitor.
 */
class IngestEsignEventAction
{
    public function __construct(
        protected EsignEventMapper $mapper,
        protected ClientHandlerAlertService $alerts,
        protected HistoryService $history,
    ) {}

    public function handle(EsignDocument $document, string $eventName, array $payload, ?string $deliveryId = null): ?EsignEvent
    {
        $mapping    = $this->mapper->map($eventName);
        $occurredAt = $this->parseOccurredAt($payload['created_at'] ?? null);

        // Idempotency key: the provider delivery id when present, else synthesized.
        $providerEventId = (string) ($deliveryId ?: implode(':', array_filter([
            $document->provider_document_id ?: (string) $document->id,
            $eventName ?: 'unknown',
            $occurredAt->timestamp,
        ])));

        $existing = EsignEvent::query()
            ->withoutGlobalScopes()
            ->where('esign_document_id', $document->id)
            ->where('provider_event_id', $providerEventId)
            ->first();

        if ($existing) {
            return $existing;
        }

        $wasCompleted = $document->completed_at !== null;

        $event = EsignEvent::create([
            'esign_document_id' => $document->id,
            'solicitor_id'      => $document->solicitor_id,
            'event_type'        => $mapping['event'],
            'mapped_status'     => $mapping['status'],
            'provider_event_id' => $providerEventId,
            'payload'           => $payload,
            'occurred_at'       => $occurredAt,
        ]);

        // Capture the document hash / certificate id when the payload carries them.
        $docData = (array) data_get($payload, 'data.document', []);
        $updates = [];
        if (!empty($docData['document_hash']) && empty($document->document_hash)) {
            $updates['document_hash'] = $docData['document_hash'];
        }
        if (!empty($docData['certificate_id']) && empty($document->certificate_id)) {
            $updates['certificate_id'] = $docData['certificate_id'];
        }
        if ($updates) {
            $document->forceFill($updates)->save();
        }

        if ($mapping['status']) {
            $timestamps = [];
            if ($mapping['timestamp_column'] && empty($document->{$mapping['timestamp_column']})) {
                $timestamps[$mapping['timestamp_column']] = $occurredAt;
            }
            $document->transitionStatus($mapping['status'], $timestamps);
        }

        // Document just completed → fetch the signed PDF + certificate; sync CFA dates.
        if (!$wasCompleted && $mapping['event'] === EsignEventType::COMPLETED) {
            DownloadSignedEsignDocumentsJob::dispatch($document->id);
            $this->syncCfaKeyDates($document);
        }

        // Recompute the request aggregate; alert handlers once when the whole
        // request reaches COMPLETED.
        if ($request = $document->contractRequest) {
            $before = $request->status;
            $request->recomputeStatus();

            if ($before !== EsignStatus::COMPLETED && $request->status === EsignStatus::COMPLETED) {
                $this->alertRequestCompleted($request);
            }
        }

        return $event;
    }

    private function syncCfaKeyDates(EsignDocument $document): void
    {
        $client = $document->contractRequest?->client;

        if (!$client || !$client->claim) {
            return;
        }

        if (Str::slug((string) ($document->contract?->name ?? '')) !== 'cfa') {
            return;
        }

        $client->claim->keyDates()->syncWithoutDetaching([
            23 => ['date' => date('Y-m-d')],
            24 => ['date' => date('Y-m-d', strtotime('+14 day'))],
        ]);
    }

    private function alertRequestCompleted(ContractRequest $request): void
    {
        $client = $request->client;
        if (!$client) {
            return;
        }

        $documents = $request->esignDocuments()->with('contract')->get()
            ->map(fn (EsignDocument $d) => ['name' => $d->contract?->name])
            ->filter(fn ($n) => filled($n['name']))
            ->values()
            ->all();

        $this->history->add($client, 'All e-signature documents completed.', [
            'contract_request_id' => $request->id,
        ]);

        $this->alerts->initialDocumentsSigned($client, $documents);
    }

    private function parseOccurredAt(mixed $value): Carbon
    {
        if ($value === null || $value === '') {
            return now();
        }

        if (is_numeric($value)) {
            return Carbon::createFromTimestamp((int) $value);
        }

        try {
            return Carbon::parse((string) $value);
        } catch (\Throwable) {
            return now();
        }
    }
}
