<?php

namespace WPSN\Modules\Licensing;

use WPSN\Core\Http\HttpClient;
use WPSN\Core\Logger;
use WPSN\Core\Settings;

defined('ABSPATH') || exit;

final class BillingSyncClient
{
    private Settings $settings;
    private Logger $logger;
    private ServerContract $contract;

    public function __construct(Settings $settings, Logger $logger)
    {
        $this->settings = $settings;
        $this->logger = $logger;
        $this->contract = new ServerContract($settings);
    }

    /**
     * @return array{ok:bool,status?:int,error?:string,retry_after?:int,billing?:array<string,mixed>,signature?:string,alg?:string,raw?:array<string,mixed>,meta?:array<string,string>,connection?:array<string,string>,kid?:string}
     */
    public function fetch(string $licenseKey): array
    {
        $licenseKey = trim($licenseKey);
        if ($licenseKey === '') {
            return ['ok' => false, 'status' => 400, 'error' => __('License key is empty.', 'wp-seo-next')];
        }

        $serverUrl = trim((string) $this->settings->get('licensing.server_url', ''));
        if ($serverUrl === '') {
            return ['ok' => false, 'status' => 400, 'error' => __('License server URL is not configured.', 'wp-seo-next')];
        }

        $path = trim((string) $this->settings->get('licensing.billing_sync_path', '/wp-seo-next/billing/sync'));
        if ($path === '') {
            $path = '/wp-seo-next/billing/sync';
        }

        $timeout = (int) $this->settings->get('licensing.billing_sync_timeout', 15);
        $timeout = max(3, min(60, $timeout));

        $endpoint = rtrim($serverUrl, '/') . '/' . ltrim($path, '/');
        $payload = $this->contract->buildPayload('billing_sync', [
            'license_key' => $licenseKey,
        ]);

        $res = HttpClient::post($endpoint, [
            'timeout' => $timeout,
            'headers' => $this->contract->buildHeaders('licensing', 'billing_sync', $payload),
            'body' => $this->contract->jsonEncode($payload),
        ], [
            'service' => 'licensing',
            'context' => 'billing_sync',
            'retries' => 1,
            'retry_statuses' => [429, 500, 502, 503, 504],
        ]);

        if (is_wp_error($res)) {
            $retryAfter = 0;
            $data = $res->get_error_data();
            if (is_array($data) && isset($data['retry_after'])) {
                $retryAfter = (int) $data['retry_after'];
            }
            return ['ok' => false, 'status' => 0, 'error' => $res->get_error_message(), 'retry_after' => $retryAfter];
        }

        $code = (int) wp_remote_retrieve_response_code($res);
        $body = (string) wp_remote_retrieve_body($res);
        $retryAfter = $this->retryAfterFromResponse($res);
        $decoded = $body !== '' ? json_decode($body, true) : null;

        if (!is_array($decoded)) {
            $msg = $body !== '' ? $body : __('Invalid response from billing sync endpoint.', 'wp-seo-next');
            return ['ok' => false, 'status' => $code, 'error' => $msg, 'retry_after' => $retryAfter];
        }

        $meta = $this->augmentMeta(ServerContract::extractResponseMeta($decoded), $res);
        $connection = ServerContract::extractConnection($decoded);
        $kid = isset($meta['kid']) ? (string) $meta['kid'] : '';

        $ok = !array_key_exists('ok', $decoded) || !empty($decoded['ok']);
        if (!$ok) {
            $err = isset($decoded['error']) ? (string) $decoded['error'] : __('Billing sync failed.', 'wp-seo-next');
            return [
                'ok' => false,
                'status' => $code,
                'error' => $err,
                'retry_after' => $retryAfter,
                'raw' => $decoded,
                'meta' => $meta,
                'connection' => $connection,
                'kid' => $kid,
            ];
        }

        $billing = $this->extractBillingPayload($decoded);
        if (empty($billing)) {
            return [
                'ok' => false,
                'status' => $code,
                'error' => __('Billing sync response did not include a billing payload.', 'wp-seo-next'),
                'retry_after' => $retryAfter,
                'raw' => $decoded,
                'meta' => $meta,
                'connection' => $connection,
                'kid' => $kid,
            ];
        }

        return [
            'ok' => true,
            'status' => $code,
            'billing' => $billing,
            'signature' => isset($decoded['signature']) ? (string) $decoded['signature'] : '',
            'alg' => isset($decoded['alg']) ? (string) $decoded['alg'] : '',
            'raw' => $decoded,
            'meta' => $meta,
            'connection' => $connection,
            'kid' => $kid,
        ];
    }

    /**
     * @param array<string,mixed> $decoded
     * @return array<string,mixed>
     */
    private function extractBillingPayload(array $decoded): array
    {
        foreach (['billing', 'data'] as $key) {
            if (isset($decoded[$key]) && is_array($decoded[$key])) {
                return $decoded[$key];
            }
        }

        $copy = $decoded;
        unset($copy['ok'], $copy['status'], $copy['error'], $copy['signature'], $copy['alg'], $copy['message'], $copy['meta'], $copy['connection'], $copy['binding']);

        return is_array($copy) ? $copy : [];
    }

    private function retryAfterFromResponse($res): int
    {
        $hdr = wp_remote_retrieve_header($res, 'retry-after');
        if (is_string($hdr) && $hdr !== '' && is_numeric($hdr)) {
            return (int) $hdr;
        }

        return 0;
    }

    /**
     * @param array<string,string> $meta
     * @return array<string,string>
     */
    private function augmentMeta(array $meta, $res): array
    {
        $map = [
            'kid' => ['x-wpsn-key-id', 'x-wpsn-kid'],
            'trace_id' => ['x-wpsn-trace-id'],
            'request_id' => ['x-wpsn-request-id'],
            'contract_version' => ['x-wpsn-contract-version'],
            'server_time' => ['date', 'x-wpsn-server-time'],
        ];

        foreach ($map as $target => $headers) {
            if (!empty($meta[$target])) {
                continue;
            }
            foreach ($headers as $header) {
                $value = wp_remote_retrieve_header($res, $header);
                if (!is_scalar($value)) {
                    continue;
                }
                $value = trim((string) $value);
                if ($value !== '') {
                    $meta[$target] = substr($value, 0, 190);
                    break;
                }
            }
        }

        return $meta;
    }
}
