<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Logger;
use App\Models\IntegrationModel;

/**
 * TwilioClient
 *
 * Three independent responsibilities, each testable on its own:
 *   1. REST API calls (initiate outbound calls, send WhatsApp/SMS) —
 *      needs real network + credentials, graceful-failure pattern
 *      like MailService/OpenAiClient.
 *   2. TwiML generation (buildGatherResponse/buildSayResponse/...) —
 *      pure string building, fully testable offline, and this is what
 *      VoiceAgentController returns to Twilio for every inbound call.
 *   3. Webhook signature verification — pure HMAC computation per
 *      Twilio's documented algorithm, fully testable offline with a
 *      known auth token and expected signature.
 */
final class TwilioClient
{
    private const API_BASE = 'https://api.twilio.com/2010-04-01';

    public function __construct(private readonly IntegrationModel $integrations = new IntegrationModel())
    {
    }

    public function isConfigured(): bool
    {
        $creds = $this->integrations->credentialsFor('twilio');

        return $creds !== null && !empty($creds['account_sid']) && !empty($creds['auth_token']);
    }

    /** @return array{success:bool, call_sid?:string, error?:string} */
    public function initiateCall(string $toNumber, string $twimlUrl): array
    {
        $creds = $this->integrations->credentialsFor('twilio');
        if ($creds === null || empty($creds['account_sid']) || empty($creds['auth_token'])) {
            return ['success' => false, 'error' => 'Twilio is not connected. Configure it from Admin > Integrations.'];
        }

        return $this->post(
            "/Accounts/{$creds['account_sid']}/Calls.json",
            $creds,
            ['To' => $toNumber, 'From' => $creds['from_number'] ?? '', 'Url' => $twimlUrl]
        );
    }

    /** @return array{success:bool, message_sid?:string, error?:string} */
    public function sendWhatsAppMessage(string $toNumber, string $body): array
    {
        $creds = $this->integrations->credentialsFor('whatsapp') ?? $this->integrations->credentialsFor('twilio');
        if ($creds === null || empty($creds['account_sid']) || empty($creds['auth_token'])) {
            return ['success' => false, 'error' => 'WhatsApp (via Twilio) is not connected. Configure it from Admin > Integrations.'];
        }

        return $this->post(
            "/Accounts/{$creds['account_sid']}/Messages.json",
            $creds,
            [
                'To' => 'whatsapp:' . $toNumber,
                'From' => 'whatsapp:' . ($creds['whatsapp_number'] ?? $creds['from_number'] ?? ''),
                'Body' => $body,
            ]
        );
    }

    private function post(string $path, array $creds, array $formFields): array
    {
        if (!function_exists('curl_init')) {
            return ['success' => false, 'error' => 'Server is missing the PHP curl extension.'];
        }

        $ch = curl_init(self::API_BASE . $path);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($formFields),
            CURLOPT_USERPWD => $creds['account_sid'] . ':' . $creds['auth_token'],
            CURLOPT_TIMEOUT => 20,
        ]);

        $body = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $error = curl_error($ch);
        curl_close($ch);

        if ($body === false || $error !== '') {
            Logger::error('TwilioClient: request failed', ['error' => $error]);

            return ['success' => false, 'error' => "Could not reach Twilio: {$error}"];
        }

        $decoded = json_decode((string) $body, true);

        if ($status >= 300 || !is_array($decoded)) {
            $apiError = $decoded['message'] ?? "HTTP {$status}";
            Logger::error('TwilioClient: API error', ['status' => $status, 'error' => $apiError]);

            return ['success' => false, 'error' => $apiError];
        }

        return ['success' => true, 'call_sid' => $decoded['sid'] ?? null, 'message_sid' => $decoded['sid'] ?? null];
    }

    // ---- TwiML generation (pure string building, no network) ----

    public function buildSayAndHangup(string $text): string
    {
        $escaped = $this->xmlEscape($text);

        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?><Response><Say voice=\"Polly.Joanna\">{$escaped}</Say><Hangup/></Response>";
    }

    /** Says $text, then gathers speech input and posts it to $actionUrl. */
    public function buildGatherResponse(string $text, string $actionUrl, int $timeoutSeconds = 5): string
    {
        $escapedText = $this->xmlEscape($text);
        $escapedUrl = $this->xmlEscape($actionUrl);

        return '<?xml version="1.0" encoding="UTF-8"?><Response>'
            . "<Gather input=\"speech\" action=\"{$escapedUrl}\" method=\"POST\" speechTimeout=\"auto\" timeout=\"{$timeoutSeconds}\">"
            . "<Say voice=\"Polly.Joanna\">{$escapedText}</Say>"
            . '</Gather>'
            . "<Say voice=\"Polly.Joanna\">I didn't catch that. Let me connect you with someone who can help.</Say>"
            . '<Hangup/></Response>';
    }

    public function buildRecordResponse(string $text, string $actionUrl): string
    {
        $escapedText = $this->xmlEscape($text);
        $escapedUrl = $this->xmlEscape($actionUrl);

        return '<?xml version="1.0" encoding="UTF-8"?><Response>'
            . "<Say voice=\"Polly.Joanna\">{$escapedText}</Say>"
            . "<Record action=\"{$escapedUrl}\" maxLength=\"120\" playBeep=\"true\"/>"
            . '</Response>';
    }

    private function xmlEscape(string $text): string
    {
        return htmlspecialchars($text, ENT_XML1 | ENT_QUOTES, 'UTF-8');
    }

    // ---- Webhook signature verification (pure HMAC, no network) ----

    /**
     * Verifies the X-Twilio-Signature header per Twilio's documented
     * algorithm: HMAC-SHA1 of (full request URL + sorted POST param
     * key/value pairs concatenated) using the Auth Token as the key,
     * base64-encoded, compared to the header value.
     * https://www.twilio.com/docs/usage/security#validating-requests
     */
    public function verifyWebhookSignature(string $url, array $postParams, string $signatureHeader, string $authToken): bool
    {
        ksort($postParams);

        $data = $url;
        foreach ($postParams as $key => $value) {
            $data .= $key . $value;
        }

        $expected = base64_encode(hash_hmac('sha1', $data, $authToken, true));

        return hash_equals($expected, $signatureHeader);
    }
}
