<?php

namespace App\Actions\Communications;

use App\Enum\Communication\CallProvider;
use App\Enum\Communication\CommunicationChannel;
use App\Enum\Communication\CommunicationDirection;
use App\Enum\Communication\CommunicationEventType;
use App\Enum\Communication\CommunicationStatus;
use App\Models\Client\Client;
use App\Models\Communication\Communication;
use App\Models\Communication\SolicitorPhoneNumber;
use App\Models\User\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;

class CreateInboundCommunicationAction {
    public function handle(
        int                  $solicitorId,
        SolicitorPhoneNumber $calledNumber,
        string               $fromNumber,
        ?Model               $communicable = null,
        array                $payload = []
    ): Communication {
        $preferredAgent = $calledNumber->type?->slug === 'client_line'
            ? $this->resolvePreferredAgent($communicable)
            : null;

        return DB::transaction(function () use (
            $solicitorId,
            $calledNumber,
            $fromNumber,
            $communicable,
            $payload,
            $preferredAgent
        )
        {
            $communication = Communication::query()->create([
                'solicitor_id' => $solicitorId,
                'agent_id' => $preferredAgent?->id,
                'communicable_type' => $communicable?->getMorphClass(),
                'communicable_id' => $communicable?->getKey(),
                'channel' => CommunicationChannel::Voice->value,
                'direction' => CommunicationDirection::Inbound->value,
                'status' => CommunicationStatus::Initiated->value,
                'started_at' => now(),
                'preview' => $communicable
                    ? 'Inbound call received from matched caller'
                    : 'Inbound call received from unknown caller',
                'meta' => [
                    'provider' => CallProvider::Twilio->value,
                    'twilio_payload' => $payload,
                ],
            ]);

            $call = $communication->call()->create([
                'solicitor_id' => $solicitorId,
                'assigned_agent_id' => $preferredAgent?->id,
                'answered_by_agent_id' => null,
                'solicitor_phone_number_id' => $calledNumber->id,
                'provider' => CallProvider::Twilio->value,
                'provider_call_sid' => $payload['CallSid'] ?? null,
                'from_number' => $this->normalizePhoneNumber($fromNumber),
                'to_number' => $calledNumber->normalized_phone_number,
                'phone_number_type_slug' => $calledNumber->type?->slug,
                'call_type' => $this->resolveCallType($calledNumber),
                'queue_type' => $calledNumber->type?->default_queue_type,
                'target_label' => $this->resolveTargetLabel($communicable, $fromNumber),
                'target_type' => $communicable
                    ? class_basename($communicable)
                    : 'Unknown Caller',
                'is_recorded' => (bool)($calledNumber->type?->auto_record ?? true),
                'recording_notice_played' => false,
                'meta' => [
                    'twilio_call_status' => $payload['CallStatus'] ?? null,
                    'twilio_direction' => $payload['Direction'] ?? null,
                    'raw_payload' => $payload,
                ],
            ]);

            $communication->events()->create([
                'solicitor_id' => $solicitorId,
                'call_id' => $call->id,
                'agent_id' => null,
                'event' => CommunicationEventType::InboundCallReceived->value,
                'source' => 'twilio',
                'description' => $communicable
                    ? 'Inbound call received and matched to existing record.'
                    : 'Inbound call received from unknown caller.',
                'occurred_at' => now(),
                'payload' => [
                    'from_number' => $this->normalizePhoneNumber($fromNumber),
                    'to_number' => $calledNumber->normalized_phone_number,
                    'provider_call_sid' => $payload['CallSid'] ?? null,
                    'matched_model' => $communicable ? $communicable->getMorphClass() : null,
                    'matched_id' => $communicable?->getKey(),
                ],
            ]);

            return $communication->load([
                'call',
                'events',
                'communicable',
            ]);
        });
    }

    protected function resolvePreferredAgent(?Model $communicable): ?User {
        if (!$communicable instanceof Client) {
            return null;
        }

        // Most recent outbound call agent within the last hour takes priority.
        $recentCall = Communication::query()
            ->where('communicable_type', $communicable->getMorphClass())
            ->where('communicable_id', $communicable->getKey())
            ->where('channel', CommunicationChannel::Voice->value)
            ->where('direction', CommunicationDirection::Outbound->value)
            ->where('started_at', '>=', now()->subHour())
            ->whereNotNull('agent_id')
            ->latest('started_at')
            ->first();

        if ($recentCall?->agent_id) {
            return $recentCall->agent;
        }

        // Fall back to any assigned handler on the client.
        return $communicable->handlers()->with('user')->first()?->user;
    }

    protected function normalizePhoneNumber(?string $value): string {
        $value = trim((string)$value);

        if ($value === '') {
            return '';
        }

        $value = preg_replace('/[^\d+]/', '', $value) ?? '';

        if (str_starts_with($value, '00')) {
            $value = '+' . substr($value, 2);
        }

        if (!str_starts_with($value, '+')) {
            if (str_starts_with($value, '0')) {
                $value = '+44' . substr($value, 1);
            } else {
                $value = '+' . $value;
            }
        }

        return $value;
    }

    protected function resolveCallType(SolicitorPhoneNumber $calledNumber): string {
        return match ($calledNumber->type?->slug) {
            'client_line' => 'client',
            'external_line' => 'external',
            'main_inbound' => 'general_intake',
            default => 'general_intake',
        };
    }

    protected function resolveTargetLabel(?Model $communicable, string $fromNumber): string {
        if (!$communicable) {
            return $this->normalizePhoneNumber($fromNumber);
        }

        foreach (['getCallableLabel', 'getDisplayName', 'getFullName'] as $method) {
            if (method_exists($communicable, $method)) {
                return (string)$communicable->{$method}();
            }
        }

        foreach (['name', 'full_name', 'display_name', 'company_name'] as $attribute) {
            if (isset($communicable->{$attribute}) && filled($communicable->{$attribute})) {
                return (string)$communicable->{$attribute};
            }
        }

        return class_basename($communicable) . ' #' . $communicable->getKey();
    }
}