<?php

declare(strict_types=1);

namespace WhmcsMigrate;

use RuntimeException;

final class WhmcsApi
{
    private string $apiUrl;

    private string $identifier;

    private string $secret;

    private const int MAX_RETRIES = 3;

    private const array BACKOFF_SECONDS = [1, 2, 4];

    public function __construct(
        private readonly Config $config,
        private readonly Logger $logger,
    ) {
        $this->apiUrl = $config->getRequired('WHMCS_API_URL');
        $this->identifier = $config->getRequired('WHMCS_API_IDENTIFIER');
        $this->secret = $config->getRequired('WHMCS_API_SECRET');
    }

    /**
     * Call a WHMCS API action with optional parameters.
     *
     * Retries up to 3 times with exponential backoff on failure.
     *
     * @return array<string, mixed> The decoded JSON response.
     *
     * @throws RuntimeException On final failure after all retries.
     */
    public function call(string $action, array $params = []): array
    {
        $postFields = array_merge($params, [
            'identifier' => $this->identifier,
            'secret' => $this->secret,
            'action' => $action,
            'responsetype' => 'json',
        ]);

        $this->logger->debug("WHMCS API call: {$action}", [
            'params' => array_diff_key($params, ['identifier' => 1, 'secret' => 1]),
        ]);

        $lastException = null;

        for ($attempt = 0; $attempt < self::MAX_RETRIES; $attempt++) {
            try {
                $result = $this->doRequest($postFields);

                $this->logger->debug("WHMCS API response: {$action}", [
                    'result' => $result['result'] ?? 'unknown',
                ]);

                return $result;
            } catch (RuntimeException $e) {
                $lastException = $e;
                $backoff = self::BACKOFF_SECONDS[$attempt] ?? 4;

                $retryMessage = sprintf(
                    'WHMCS API call failed (attempt %d/%d): %s, retrying in %ds',
                    $attempt + 1,
                    self::MAX_RETRIES,
                    $e->getMessage(),
                    $backoff,
                );
                $this->logger->warning($retryMessage);

                if ($attempt < self::MAX_RETRIES - 1) {
                    sleep($backoff);
                }
            }
        }

        throw new RuntimeException(
            "WHMCS API call '{$action}' failed after " . self::MAX_RETRIES . " attempts: " . $lastException->getMessage(),
            0,
            $lastException,
        );
    }

    /**
     * Execute the cURL request to the WHMCS API.
     *
     * @return array<string, mixed>
     *
     * @throws RuntimeException On cURL or JSON decode error.
     */
    private function doRequest(array $postFields): array
    {
        $ch = curl_init();

        curl_setopt_array($ch, [
            CURLOPT_URL => $this->apiUrl,
            CURLOPT_POST => true,
            CURLOPT_POSTFIELDS => http_build_query($postFields),
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 60,
            CURLOPT_CONNECTTIMEOUT => 15,
            CURLOPT_SSL_VERIFYPEER => true,
            CURLOPT_IPRESOLVE => CURL_IPRESOLVE_V4,
            CURLOPT_HTTPHEADER => [
                'Content-Type: application/x-www-form-urlencoded',
                'Accept: application/json',
            ],
        ]);

        $response = curl_exec($ch);
        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
        $curlError = curl_error($ch);
        $curlErrno = curl_errno($ch);

        curl_close($ch);

        if ($curlErrno !== 0) {
            throw new RuntimeException("cURL error ({$curlErrno}): {$curlError}");
        }

        if ($httpCode < 200 || $httpCode >= 300) {
            throw new RuntimeException("HTTP {$httpCode} response from WHMCS API");
        }

        if (! is_string($response) || $response === '') {
            throw new RuntimeException('Empty response from WHMCS API');
        }

        $decoded = json_decode($response, true);

        if (json_last_error() !== JSON_ERROR_NONE) {
            throw new RuntimeException('Failed to decode WHMCS API response: ' . json_last_error_msg());
        }

        if (isset($decoded['result']) && $decoded['result'] === 'error') {
            throw new RuntimeException('WHMCS API error: ' . ($decoded['message'] ?? 'Unknown error'));
        }

        return $decoded;
    }
}
