<?php

namespace App\Libraries;

use App\Libraries\Contracts\CredentialStoreInterface;
use CodeIgniter\HTTP\CURLRequest;
use RuntimeException;
use Throwable;

/**
 * Wraps Hubtel Checkout (payment collection) and Hubtel SMS. Every call
 * is logged (both the request shape and Hubtel's raw response) — per the
 * Phase 3 brief, this matters for debugging and because a hotel owner
 * will ask "did the guest actually get billed / get the message?"
 *
 * Credentials come from a CredentialStoreInterface, never from .env —
 * typically the current tenant's Settings (tenant_settings, that hotel's
 * own Hubtel account), but the platform also uses this same class with
 * PlatformSettings (platform_settings) to collect subscription payments
 * FROM hotels — see Config\Services::platformHubtelService(). Whichever
 * store is injected, a tenant/platform with nothing configured yet gets a
 * clear exception rather than a confusing HTTP failure against Hubtel
 * with empty credentials.
 */
class HubtelService
{
    private const CHECKOUT_ENDPOINT = 'https://payproxyapi.hubtel.com/items/initiate';
    private const SMS_ENDPOINT      = 'https://sms.hubtel.com/v1/messages/send';

    // Every outbound call here happens synchronously inside a web request
    // (a staff member clicking "Generate Payment Link", a booking being
    // created, ...). Without an explicit timeout, a stalled connection to
    // Hubtel would hang the whole request up to PHP's max_execution_time
    // (30s default) — connect/response caps keep a slow network fast-failing
    // into the existing RuntimeException handling instead.
    private const CONNECT_TIMEOUT = 5;
    private const REQUEST_TIMEOUT = 10;

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

    /**
     * Creates a Hubtel Checkout invoice and returns the hosted checkout
     * URL to send the guest. Hubtel calls `callbackUrl` (machine-to-
     * machine, no session) when the guest completes or abandons payment
     * — see PaymentWebhookController.
     *
     * @return array{checkoutUrl: string, checkoutId: ?string}
     */
    public function createCheckout(
        float $amount,
        string $description,
        string $clientReference,
        string $callbackUrl,
        string $returnUrl,
        string $cancellationUrl,
    ): array {
        $clientId     = $this->settings->get('hubtel.client_id');
        $clientSecret = $this->settings->get('hubtel.client_secret');
        $merchantId   = $this->settings->get('hubtel.merchant_account_number');

        if (! $clientId || ! $clientSecret || ! $merchantId) {
            // Generic wording on purpose: this same exception fires for a
            // tenant's own Hubtel account (Settings) and the platform's
            // (PlatformSettings) — see CredentialStoreInterface — so it
            // can't name a specific settings page without knowing which
            // credential store was injected.
            throw new RuntimeException('Hubtel Checkout is not configured yet — add your Hubtel credentials under Settings.');
        }

        // CI4's CURLRequest throws CodeIgniter\HTTP\Exceptions\HTTPException
        // (not RuntimeException) on a transport-level failure — DNS
        // failure, connection refused, timeout exceeded. Normalized to
        // RuntimeException here so every caller only ever has one
        // exception type to catch, whether Hubtel responded with an error
        // or didn't respond at all.
        try {
            $response = $this->client->request('POST', self::CHECKOUT_ENDPOINT, [
                'auth'    => [$clientId, $clientSecret],
                'headers' => ['Content-Type' => 'application/json'],
                'json'    => [
                    'totalAmount'           => $amount,
                    'description'           => $description,
                    'callbackUrl'           => $callbackUrl,
                    'returnUrl'             => $returnUrl,
                    'cancellationUrl'       => $cancellationUrl,
                    'merchantAccountNumber' => $merchantId,
                    'clientReference'       => $clientReference,
                ],
                'connect_timeout' => self::CONNECT_TIMEOUT,
                'timeout'         => self::REQUEST_TIMEOUT,
                'http_errors'     => false,
            ]);
        } catch (Throwable $e) {
            log_message('error', 'Hubtel Checkout initiate: connection failure — ' . $e->getMessage());

            throw new RuntimeException('Could not reach Hubtel — please try again in a moment.');
        }

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

        if (($body['responseCode'] ?? null) !== '0000' || empty($body['data']['checkoutUrl'])) {
            log_message('error', 'Hubtel Checkout initiate failed: ' . $response->getBody());

            throw new RuntimeException('Hubtel did not return a checkout link: ' . ($body['message'] ?? 'unknown error'));
        }

        return [
            'checkoutUrl' => $body['data']['checkoutUrl'],
            'checkoutId'  => $body['data']['checkoutId'] ?? null,
        ];
    }

    /**
     * Hubtel SMS Quick Send — used as the fallback/alternative to
     * WhatsApp for booking confirmations, pre-arrival reminders, and
     * post-stay feedback requests.
     *
     * @return array{success: bool, raw: string}
     */
    public function sendSms(string $toPhone, string $message): array
    {
        $clientId     = $this->settings->get('hubtel.sms.client_id');
        $clientSecret = $this->settings->get('hubtel.sms.client_secret');
        $senderId     = $this->settings->get('hubtel.sms.sender_id');

        if (! $clientId || ! $clientSecret || ! $senderId) {
            throw new RuntimeException('Hubtel SMS is not configured yet — add your Hubtel credentials under Settings.');
        }

        try {
            $response = $this->client->request('GET', self::SMS_ENDPOINT, [
                'query' => [
                    'From'         => $senderId,
                    'To'           => $toPhone,
                    'Content'      => $message,
                    'ClientId'     => $clientId,
                    'ClientSecret' => $clientSecret,
                ],
                'connect_timeout' => self::CONNECT_TIMEOUT,
                'timeout'         => self::REQUEST_TIMEOUT,
                'http_errors'     => false,
            ]);
        } catch (Throwable $e) {
            log_message('error', 'Hubtel SMS send: 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()];
    }
}
