<?php

namespace App\Actions\Communications;

use App\Services\Communications\Routing\CallRoutingEngine;
use App\Services\Communications\Routing\RoutingContext;
use Twilio\TwiML\VoiceResponse;

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

    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);
    }

    protected function buildTwiml($decision): 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);
                $dial->client('agent_' . $decision->agentId, $agentClientOptions);
                break;

            case 'dial_agents':
                $dial = $voice->dial('', $agentDialOptions);
                foreach ($decision->agentIds as $agentId) {
                    $dial->client('agent_' . $agentId, $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':
                $voice->say('Please leave a message after the tone.');
                $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;
    }
}