<?php

namespace App\Libraries;

use CodeIgniter\HTTP\CURLRequest;
use RuntimeException;
use Throwable;

/**
 * Meta WhatsApp Cloud API — sends messages directly against Meta's Graph
 * API (no third-party BSP subscription). Credentials come from the
 * current tenant's Settings, same as HubtelService.
 *
 * sendTemplate() is for proactive outbound messages (confirmations,
 * reminders) — those require an approved template, since there's no
 * open customer-service window yet. sendFreeformText() is for replying
 * to a guest who just messaged us (see ConciergeWebhookController) —
 * Meta opens a 24h freeform-reply window the moment a guest sends an
 * inbound message, so a reply within that window can be plain text with
 * no template. Sending sendFreeformText() outside that window is a
 * caller error (Meta will reject it) — only the concierge's own
 * inbound-triggered reply path should ever call it.
 */
class WhatsAppService
{
    private const API_VERSION = 'v21.0';

    // See HubtelService's identical constants for why: no explicit
    // timeout means a stalled connection hangs the whole web request up
    // to PHP's max_execution_time instead of failing fast.
    private const CONNECT_TIMEOUT = 5;
    private const REQUEST_TIMEOUT = 10;

    public function __construct(
        private readonly Settings $settings,
        private readonly CURLRequest $client,
    ) {
    }

    /**
     * @param list<string> $bodyParams Positional {{1}}, {{2}}, ... values for the template body
     *
     * @return array{success: bool, raw: string}
     */
    public function sendTemplate(string $toPhone, string $templateName, string $languageCode, array $bodyParams = []): array
    {
        $phoneNumberId = $this->settings->get('whatsapp.phone_number_id');
        $accessToken   = $this->settings->get('whatsapp.access_token');

        if (! $phoneNumberId || ! $accessToken) {
            throw new RuntimeException('WhatsApp is not configured for this hotel yet — set it up under Integration Settings.');
        }

        $components = [];

        if ($bodyParams !== []) {
            $components[] = [
                'type'       => 'body',
                'parameters' => array_map(static fn ($value) => ['type' => 'text', 'text' => (string) $value], $bodyParams),
            ];
        }

        return $this->post($phoneNumberId, $accessToken, [
            'messaging_product' => 'whatsapp',
            'to'                => ltrim($toPhone, '+'),
            'type'              => 'template',
            'template'          => [
                'name'       => $templateName,
                'language'   => ['code' => $languageCode],
                'components' => $components,
            ],
        ]);
    }

    /**
     * Plain-text reply, only valid within the 24h window a guest's own
     * inbound message opens. See ConciergeWebhookController — this is
     * how the concierge actually replies on WhatsApp.
     *
     * @return array{success: bool, raw: string}
     */
    public function sendFreeformText(string $toPhone, string $text): array
    {
        $phoneNumberId = $this->settings->get('whatsapp.phone_number_id');
        $accessToken   = $this->settings->get('whatsapp.access_token');

        if (! $phoneNumberId || ! $accessToken) {
            throw new RuntimeException('WhatsApp is not configured for this hotel yet — set it up under Integration Settings.');
        }

        return $this->post($phoneNumberId, $accessToken, [
            'messaging_product' => 'whatsapp',
            'to'                => ltrim($toPhone, '+'),
            'type'              => 'text',
            'text'              => ['body' => $text],
        ]);
    }

    /**
     * "Test Connection" on Settings > Integrations — verifies the
     * configured Phone Number ID/Access Token actually authenticate
     * against Meta, WITHOUT sending a real message. Deliberately not a
     * real test send: sendTemplate() needs a template the tenant has
     * already created and had Meta approve (this codebase has never
     * been able to assume a specific template name exists — see
     * MessagingService's own doc comment on that), and
     * sendFreeformText() only works within the 24h window a guest's own
     * inbound message opens, which a manager clicking a settings button
     * never has. A plain GET on the phone number's own profile proves
     * the credentials are valid and returns real, useful confirmation
     * (the actual verified display number/business name) with no
     * message-sending assumptions at all.
     *
     * @return array{success: bool, displayPhoneNumber: ?string, verifiedName: ?string, error: ?string}
     */
    public function verifyCredentials(): array
    {
        $phoneNumberId = $this->settings->get('whatsapp.phone_number_id');
        $accessToken   = $this->settings->get('whatsapp.access_token');

        if (! $phoneNumberId || ! $accessToken) {
            return ['success' => false, 'displayPhoneNumber' => null, 'verifiedName' => null, 'error' => 'WhatsApp is not configured yet — fill in the Phone Number ID and Access Token first.'];
        }

        $url = "https://graph.facebook.com/" . self::API_VERSION . "/{$phoneNumberId}?fields=display_phone_number,verified_name";

        try {
            $response = $this->client->request('GET', $url, [
                'headers' => ['Authorization' => 'Bearer ' . $accessToken],
                'connect_timeout' => self::CONNECT_TIMEOUT,
                'timeout'         => self::REQUEST_TIMEOUT,
                'http_errors'     => false,
            ]);
        } catch (Throwable $e) {
            return ['success' => false, 'displayPhoneNumber' => null, 'verifiedName' => null, 'error' => 'Connection failure: ' . $e->getMessage()];
        }

        $status = $response->getStatusCode();
        $body   = json_decode((string) $response->getBody(), true) ?? [];

        if ($status < 200 || $status >= 300) {
            return ['success' => false, 'displayPhoneNumber' => null, 'verifiedName' => null, 'error' => $body['error']['message'] ?? "Meta returned HTTP {$status}."];
        }

        return [
            'success'            => true,
            'displayPhoneNumber' => $body['display_phone_number'] ?? null,
            'verifiedName'       => $body['verified_name'] ?? null,
            'error'              => null,
        ];
    }

    /**
     * @return array{success: bool, raw: string}
     */
    private function post(string $phoneNumberId, string $accessToken, array $payload): array
    {
        $url = "https://graph.facebook.com/" . self::API_VERSION . "/{$phoneNumberId}/messages";

        try {
            $response = $this->client->request('POST', $url, [
                'headers' => [
                    'Authorization' => 'Bearer ' . $accessToken,
                    'Content-Type'  => 'application/json',
                ],
                'json'            => $payload,
                'connect_timeout' => self::CONNECT_TIMEOUT,
                'timeout'         => self::REQUEST_TIMEOUT,
                'http_errors'     => false,
            ]);
        } catch (Throwable $e) {
            log_message('error', 'WhatsAppService: connection failure — ' . $e->getMessage());

            return ['success' => false, 'raw' => 'Connection failure: ' . $e->getMessage()];
        }

        $status  = $response->getStatusCode();
        $success = $status >= 200 && $status < 300;

        return ['success' => $success, 'raw' => (string) $response->getBody()];
    }
}
