<?php

namespace App\Actions\Communications;

use App\Enum\Communication\CommunicationEventType;
use App\Enum\Communication\CommunicationStatus;
use App\Models\Communication\Call;
use Illuminate\Support\Facades\DB;

class HandleCallStatusCallbackAction {
    public function handle(array $payload): ?Call {
        $callSid = $payload['CallSid'] ?? null;

        if (!$callSid) {
            return null;
        }

        /** @var Call|null $call */
        $call = Call::query()
            ->with('communication')
            ->where('provider_call_sid', $callSid)
            ->first();

        // For SDK browser calls the status callback fires on the PSTN child leg,
        // whose CallSid differs from the parent SDK call SID we stored. Fall back
        // to matching on ParentCallSid so status updates are never lost.
        $foundViaParent = false;
        if (! $call) {
            $parentCallSid = $payload['ParentCallSid'] ?? null;
            if ($parentCallSid) {
                $call = Call::query()
                    ->with('communication')
                    ->where('provider_call_sid', $parentCallSid)
                    ->first();
                $foundViaParent = (bool) $call;
            }
        }

        if (!$call || !$call->communication) {
            return null;
        }

        return DB::transaction(function () use ($call, $payload, $foundViaParent)
        {
            $communication = $call->communication;
            $status = $payload['CallStatus'] ?? null;
            $eventType = $this->mapEventType($status);
            $communicationStatus = $this->mapCommunicationStatus($status);

            $callMeta = $call->meta ?? [];
            $callMeta['twilio_status_callbacks'][] = $payload;

            // Store the PSTN child leg SID so TransferCallAction can redirect the
            // correct leg on outbound calls without a separate Twilio API lookup.
            if ($foundViaParent && empty($callMeta['child_call_sid'])) {
                $callMeta['child_call_sid'] = $payload['CallSid'];
            }

            $call->update([
                'meta' => $callMeta,
                'connected_at' => $status === 'in-progress' && !$call->connected_at
                    ? now()
                    : $call->connected_at,
            ]);

            $communicationUpdate = [
                'meta' => array_merge($communication->meta ?? [], [
                    'last_twilio_status' => $status,
                    'last_twilio_status_payload' => $payload,
                ]),
            ];

            if ($communicationStatus) {
                $communicationUpdate['status'] = $communicationStatus->value;
            }

            if ($status === 'in-progress' && !$communication->answered_at) {
                $communicationUpdate['answered_at'] = now();
            }

            if (in_array($status, ['completed', 'busy', 'failed', 'no-answer', 'canceled'], true)) {
                $communicationUpdate['ended_at'] = now();

                if (!empty($payload['CallDuration'])) {
                    $communicationUpdate['duration'] = (int)$payload['CallDuration'];
                }
            }

            $communication->update($communicationUpdate);

            if ($eventType) {
                $communication->events()->create([
                    'solicitor_id' => $communication->solicitor_id,
                    'call_id' => $call->id,
                    'agent_id' => $communication->agent_id,
                    'event' => $eventType->value,
                    'source' => 'twilio',
                    'description' => $this->buildDescription($status),
                    'occurred_at' => now(),
                    'payload' => $payload,
                ]);
            }

            return $call->fresh(['communication', 'communication.events']);
        });
    }

    protected function mapEventType(?string $status): ?CommunicationEventType {
        return match ($status) {
            'queued' => CommunicationEventType::CallQueued,
            'initiated' => CommunicationEventType::CallInitiated,
            'ringing' => CommunicationEventType::CallRinging,
            'answered' => CommunicationEventType::CallAnswered,
            'in-progress' => CommunicationEventType::CallConnected,
            'completed' => CommunicationEventType::CallCompleted,
            'busy', 'failed', 'canceled', 'no-answer' => CommunicationEventType::CallFailed,
            default => null,
        };
    }

    protected function mapCommunicationStatus(?string $status): ?CommunicationStatus {
        return match ($status) {
            'queued', 'initiated' => CommunicationStatus::Initiated,
            'ringing' => CommunicationStatus::Ringing,
            'in-progress' => CommunicationStatus::InProgress,
            'completed' => CommunicationStatus::Completed,
            // "Didn't reach the recipient" → Missed. Mobile carriers usually
            // report a declined call as `busy`, and an agent-side hangup
            // before the recipient answers comes through as `canceled`.
            // Both are "missed call" from the contact's perspective.
            'busy', 'canceled', 'no-answer' => CommunicationStatus::Missed,
            // Only a genuine Twilio/network failure is treated as Failed.
            'failed' => CommunicationStatus::Failed,
            default => null,
        };
    }

    protected function buildDescription(?string $status): string {
        return match ($status) {
            'queued' => 'Twilio queued the call.',
            'initiated' => 'Twilio initiated the call.',
            'ringing' => 'The destination is ringing.',
            'answered' => 'The call was answered.',
            'in-progress' => 'The call is in progress.',
            'completed' => 'The call completed.',
            'busy' => 'The destination was busy.',
            'failed' => 'The call failed.',
            'no-answer' => 'The destination did not answer.',
            'canceled' => 'The call was canceled.',
            default => 'Twilio sent a call status update.',
        };
    }
}