<?php

namespace App\Actions\Communications;

use App\Enum\Communication\CommunicationStatus;
use App\Services\Communications\Routing\CallRoutingEngine;
use App\Services\Communications\Routing\RoutingContext;
use App\Services\Communications\MissedCallTaskService;
use App\Services\Communications\TwilioClientIdentity;
use Illuminate\Support\Facades\Log;
use Twilio\TwiML\VoiceResponse;

class HandleInboundCallAction {
    public function __construct(
        protected ResolveCalledNumberAction        $resolveCalledNumber,
        protected ResolveCallerCommunicableAction  $resolveCallerCommunicable,
        protected CreateInboundCommunicationAction $createInboundCommunication,
        protected CallRoutingEngine                $routingEngine,
        protected MissedCallTaskService             $missedCallTasks,
    ) {
    }

    public function handle(string $fromNumber, string $toNumber, array $payload = []): string {
        $calledNumber = $this->resolveCalledNumber->handle($toNumber);
        $communicable = $this->resolveCallerCommunicable->handle(
            solicitorId: $calledNumber->solicitor_id,
            fromNumber: $fromNumber,
        );

        $communication = $this->createInboundCommunication->handle(
            solicitorId: $calledNumber->solicitor_id,
            calledNumber: $calledNumber,
            fromNumber: $fromNumber,
            communicable: $communicable,
            payload: $payload,
        );

        $context = new RoutingContext(
            solicitor: $calledNumber->solicitor,
            calledNumber: $calledNumber,
            fromNumber: $fromNumber,
            toNumber: $toNumber,
            communicable: $communicable,
            communication: $communication,
        );

        $decision = $this->routingEngine->resolve($context);

        return $this->buildTwiml($decision, $context);
    }

    protected function buildTwiml($decision, $context): string {
        $voice = new VoiceResponse();

        $recordingStatusCallback = route('twilio.voice.recording');
        $dialFallback            = route('twilio.voice.dial.fallback');
        $statusCallback          = route('twilio.voice.status');

        // Shared <Dial> options for agent-targeted routing. The `action` URL
        // receives a callback when the dial completes — used to fall through
        // to voicemail when no agent answers within the timeout (or all
        // available agents decline via the browser SDK).
        $agentDialOptions = [
            'timeout'                       => 20,
            'action'                        => $dialFallback,
            'method'                        => 'POST',
            'record'                        => 'record-from-answer-dual',
            'recordingStatusCallback'       => $recordingStatusCallback,
            'recordingStatusCallbackMethod' => 'POST',
            'recordingStatusCallbackEvent'  => 'completed',
        ];

        // Per-leg status callback so we learn when an agent actually *answers*.
        // The inbound (parent) leg never reports its answer to us, so without
        // this `answered_at` stays null and HandleRecordingStatusCallbackAction
        // misfiles the recording of a genuinely answered call as a voicemail.
        // We subscribe to `answered` only: legs that lose the ring-all race
        // never fire, so they cannot downgrade an answered call's status.
        $agentClientOptions = [
            'statusCallbackEvent'  => 'answered',
            'statusCallback'       => $statusCallback,
            'statusCallbackMethod' => 'POST',
        ];

        switch ($decision->action) {
            case 'dial_agent':
                $dial = $voice->dial('', $agentDialOptions);
                $identity = TwilioClientIdentity::for($decision->agentId);
                // Log the exact <Client> identity we hand Twilio so an instant
                // "no-answer" (device not registered under this name/account)
                // can be told apart from a real ring-timeout. Compare this
                // string to what the agent's browser registered with (the
                // `identity` in /twilio/sdk/token).
                Log::debug('HandleInboundCallAction: dialing agent client', [
                    'agent_id' => $decision->agentId,
                    'identity' => $identity,
                ]);
                $dial->client($identity, $agentClientOptions);
                break;

            case 'dial_agents':
                $dial = $voice->dial('', $agentDialOptions);
                $identities = [];
                foreach ($decision->agentIds as $agentId) {
                    $identity = TwilioClientIdentity::for($agentId);
                    $identities[$agentId] = $identity;
                    $dial->client($identity, $agentClientOptions);
                }
                break;

            case 'enqueue':
                // Queues manage their own waiting time, so we leave the
                // timeout / voicemail fallback off here intentionally.
                $dial = $voice->dial('', [
                    'record'                        => 'record-from-answer-dual',
                    'recordingStatusCallback'       => $recordingStatusCallback,
                    'recordingStatusCallbackMethod' => 'POST',
                    'recordingStatusCallbackEvent'  => 'completed',
                ]);
                $dial->queue('queue_' . $decision->queueId);
                break;

            case 'voicemail':
                // Routing directly to voicemail means nobody is available
                // (including outside business hours). Diarise the callback now;
                $context->communication->update([
                    'status' => CommunicationStatus::Missed->value,
                ]);
                $this->missedCallTasks->diarise($context->communication);

                $solicitor = $context->solicitor;
                $greetingUrl = $context->calledNumber->voicemailGreetingUrl();
                if ($greetingUrl) {
                    $voice->play($greetingUrl);
                } else {
                    $voice->say('Hi thank you for calling ' . $solicitor->name . '. I’m afraid all our staff are busy right now. Please leave a message with your name and contact details and we will call you back as soon as possible');
                }
                $voice->record([
                    'maxLength'                     => 120,
                    'playBeep'                      => true,
                    'recordingStatusCallback'       => $recordingStatusCallback,
                    'recordingStatusCallbackMethod' => 'POST',
                    'recordingStatusCallbackEvent'  => 'completed',
                ]);
                break;

            default:
                $voice->say('We are unable to connect your call at this time.');
                break;
        }

        return (string) $voice;
    }
}