<?php

namespace App\Actions\Communications;

use App\Enum\Communication\CommunicationEventType;
use App\Enum\Communication\CommunicationStatus;
use App\Jobs\DownloadAndStoreRecordingJob;
use App\Models\Communication\Call;
use App\Models\Communication\Recording;
use App\Models\Communication\Voicemail;
use App\Services\Communications\MissedCallTaskService;
use Illuminate\Support\Facades\DB;

class HandleRecordingStatusCallbackAction {
    public function __construct(
        private readonly MissedCallTaskService $missedCallTasks,
    ) {
    }

    public function handle(array $payload): ?Recording {
        $callSid = $payload['CallSid'] ?? null;
        $recordingSid = $payload['RecordingSid'] ?? null;

        if (!$callSid || !$recordingSid) {
            return null;
        }

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

        if (! $call) {
            $parentCallSid = $payload['ParentCallSid'] ?? null;
            if ($parentCallSid) {
                $call = Call::query()
                    ->with('communication')
                    ->where('provider_call_sid', $parentCallSid)
                    ->first();
            }
        }

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

        return DB::transaction(function () use ($call, $payload, $recordingSid, $callSid)
        {
            $communication = $call->communication;

            $recording = Recording::query()->updateOrCreate(
                [
                    'provider_recording_sid' => $recordingSid,
                ],
                [
                    'communication_id' => $communication->id,
                    'call_id' => $call->id,
                    'solicitor_id' => $communication->solicitor_id,
                    'provider' => $call->provider ?? 'twilio',
                    'provider_call_sid' => $callSid,
                    'status' => $payload['RecordingStatus'] ?? 'completed',
                    'duration' => isset($payload['RecordingDuration'])
                        ? (int)$payload['RecordingDuration']
                        : null,
                    'recording_url' => $payload['RecordingUrl'] ?? null,
                    'recorded_at' => now(),
                    'meta' => $payload,
                ]
            );

            // Mark as voicemail if the call was never answered by an agent.
            // Includes `Missed` because the dial-fallback controller marks
            // inbound calls as Missed before voicemail recording begins —
            // a recording arriving promotes that to Voicemail.
            $isVoicemail = $communication->answered_at === null
                && in_array($communication->status, [
                    CommunicationStatus::Initiated->value,
                    CommunicationStatus::Ringing->value,
                    CommunicationStatus::Missed->value,
                    CommunicationStatus::Voicemail->value,
                ]);

            if ($isVoicemail && $communication->status !== CommunicationStatus::Voicemail->value) {
                $communication->update(['status' => CommunicationStatus::Voicemail->value]);
            }

            // Populate the Voicemail row the dialler dashboard reads from. We
            // keyed updateOrCreate on provider_recording_sid so retried Twilio
            // callbacks don't duplicate rows.
            if ($isVoicemail && ($payload['RecordingStatus'] ?? null) === 'completed') {
                Voicemail::updateOrCreate(
                    ['provider_recording_sid' => $recordingSid],
                    [
                        'communication_id'  => $communication->id,
                        'call_id'           => $call->id,
                        'solicitor_id'      => $communication->solicitor_id,
                        'assigned_agent_id' => $call->assigned_agent_id,
                        'provider'          => $call->provider ?? 'twilio',
                        'from_number'       => $call->from_number,
                        'to_number'         => $call->to_number,
                        'recording_url'     => $payload['RecordingUrl'] ?? null,
                        'duration'          => isset($payload['RecordingDuration'])
                            ? (int) $payload['RecordingDuration']
                            : null,
                        'received_at'       => now(),
                        'meta'              => ['source' => 'recording_callback'],
                    ]
                );

                // The missed-call task already exists in the normal flow. Add
                // it here as a safety net and change its copy to tell the
                // Agent that audio is ready to review.
                $this->missedCallTasks->diarise($communication, hasVoicemail: true);
            }

            $communication->events()->create([
                'solicitor_id' => $communication->solicitor_id,
                'call_id' => $call->id,
                'agent_id' => $communication->agent_id,
                'event' => CommunicationEventType::RecordingCompleted->value,
                'source' => 'twilio',
                'description' => 'Call recording is available.',
                'occurred_at' => now(),
                'payload' => [
                    'provider_recording_sid' => $recordingSid,
                    'recording_url' => $payload['RecordingUrl'] ?? null,
                    'recording_status' => $payload['RecordingStatus'] ?? null,
                    'recording_duration' => $payload['RecordingDuration'] ?? null,
                ],
            ]);

            // Dispatch the download job only once the recording is fully ready.
            if (($payload['RecordingStatus'] ?? null) === 'completed') {
                DownloadAndStoreRecordingJob::dispatch($recording->id);
            }

            return $recording;
        });
    }
}