<?php

namespace WPSN\Modules\Licensing;

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

defined('ABSPATH') || exit;

final class SelfServeUpgradeClient
{
    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,catalog?:array<string,mixed>,signature?:string,alg?:string,raw?:array<string,mixed>,message?:string,meta?:array<string,string>,connection?:array<string,string>,kid?:string,retry_after?:int}
     */
    public function fetchCatalog(string $licenseKey): array
    {
        return $this->postJson(
            (string) $this->settings->get('licensing.self_serve_catalog_path', '/wp-seo-next/billing/catalog'),
            'self_serve_catalog',
            array_merge($this->basePayload($licenseKey), [
                'billing_source' => 'self_serve',
            ]),
            'catalog'
        );
    }

    /**
     * @return array{ok:bool,status?:int,error?:string,checkout?:array<string,mixed>,billing?:array<string,mixed>,catalog?:array<string,mixed>,management?:array<string,mixed>,signature?:string,alg?:string,raw?:array<string,mixed>,message?:string,meta?:array<string,string>,connection?:array<string,string>,kid?:string,retry_after?:int}
     */
    public function startCheckout(string $licenseKey, string $planId, string $interval = '', string $coupon = ''): array
    {
        $payload = array_merge($this->basePayload($licenseKey), [
            'plan_id' => sanitize_key($planId),
            'interval' => sanitize_key($interval),
            'coupon' => trim($coupon),
            'embed_preferred' => !empty($this->settings->get('licensing.billing_embed', false)),
            'return_url' => admin_url('tools.php?page=wpsn-license&tab=billing'),
        ]);

        return $this->postJson(
            (string) $this->settings->get('licensing.self_serve_checkout_path', '/wp-seo-next/billing/checkout'),
            'self_serve_checkout',
            $payload,
            'checkout'
        );
    }

    /**
     * @return array{ok:bool,status?:int,error?:string,checkout?:array<string,mixed>,billing?:array<string,mixed>,catalog?:array<string,mixed>,management?:array<string,mixed>,signature?:string,alg?:string,raw?:array<string,mixed>,message?:string,meta?:array<string,string>,connection?:array<string,string>,kid?:string,retry_after?:int}
     */
    public function performAction(string $licenseKey, string $action, string $planId = '', string $interval = ''): array
    {
        $payload = array_merge($this->basePayload($licenseKey), [
            'action' => sanitize_key($action),
            'plan_id' => sanitize_key($planId),
            'interval' => sanitize_key($interval),
        ]);

        return $this->postJson(
            (string) $this->settings->get('licensing.self_serve_action_path', '/wp-seo-next/billing/action'),
            'self_serve_action',
            $payload,
            'action'
        );
    }

    /**
     * @return array{ok:bool,status?:int,error?:string,billing?:array<string,mixed>,catalog?:array<string,mixed>,management?:array<string,mixed>,signature?:string,alg?:string,raw?:array<string,mixed>,message?:string,meta?:array<string,string>,connection?:array<string,string>,kid?:string,retry_after?:int}
     */
    public function fetchManagementState(string $licenseKey): array
    {
        return $this->postJson(
            (string) $this->settings->get('licensing.self_serve_manage_path', '/wp-seo-next/billing/manage'),
            'self_serve_manage',
            array_merge($this->basePayload($licenseKey), [
                'operation' => 'fetch',
            ]),
            'management'
        );
    }

    /**
     * @param array<string,mixed> $params
     * @return array{ok:bool,status?:int,error?:string,checkout?:array<string,mixed>,billing?:array<string,mixed>,catalog?:array<string,mixed>,management?:array<string,mixed>,signature?:string,alg?:string,raw?:array<string,mixed>,message?:string,meta?:array<string,string>,connection?:array<string,string>,kid?:string,retry_after?:int}
     */
    public function performManagementOperation(string $licenseKey, string $operation, array $params = []): array
    {
        $payload = array_merge($this->basePayload($licenseKey), $params, [
            'operation' => sanitize_key($operation),
        ]);

        return $this->postJson(
            (string) $this->settings->get('licensing.self_serve_action_path', '/wp-seo-next/billing/action'),
            'self_serve_management_operation',
            $payload,
            'management'
        );
    }

    /**
     * @param array<string,mixed> $payload
     * @return array{ok:bool,status?:int,error?:string,checkout?:array<string,mixed>,billing?:array<string,mixed>,catalog?:array<string,mixed>,management?:array<string,mixed>,signature?:string,alg?:string,raw?:array<string,mixed>,message?:string,meta?:array<string,string>,connection?:array<string,string>,kid?:string,retry_after?:int}
     */
    private function postJson(string $path, string $context, array $payload, string $kind): array
    {
        $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($path);
        if ($path === '') {
            return ['ok' => false, 'status' => 400, 'error' => __('Self-serve endpoint path is not configured.', 'wp-seo-next')];
        }

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

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

        if (is_wp_error($res)) {
            return ['ok' => false, 'status' => 0, 'error' => $res->get_error_message()];
        }

        $code = (int) wp_remote_retrieve_response_code($res);
        $body = (string) wp_remote_retrieve_body($res);
        $decoded = $body !== '' ? json_decode($body, true) : null;
        $retryAfter = $this->retryAfterFromResponse($res);
        if (!is_array($decoded)) {
            $msg = $body !== '' ? $body : __('Invalid response from self-serve billing 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'] : __('Self-serve billing request failed.', 'wp-seo-next');
            return ['ok' => false, 'status' => $code, 'error' => $err, 'raw' => $decoded, 'meta' => $meta, 'connection' => $connection, 'kid' => $kid, 'retry_after' => $retryAfter];
        }

        $out = [
            'ok' => true,
            'status' => $code,
            'signature' => isset($decoded['signature']) ? (string) $decoded['signature'] : '',
            'alg' => isset($decoded['alg']) ? (string) $decoded['alg'] : '',
            'raw' => $decoded,
            'message' => isset($decoded['message']) ? (string) $decoded['message'] : '',
            'meta' => $meta,
            'connection' => $connection,
            'kid' => $kid,
            'retry_after' => $retryAfter,
        ];

        if (isset($decoded['catalog']) && is_array($decoded['catalog'])) {
            $out['catalog'] = $decoded['catalog'];
        }
        if (isset($decoded['billing']) && is_array($decoded['billing'])) {
            $out['billing'] = $decoded['billing'];
        }

        foreach (['management', 'account', 'subscription_state'] as $key) {
            if (isset($decoded[$key]) && is_array($decoded[$key])) {
                $out['management'] = $decoded[$key];
                break;
            }
        }
        if (empty($out['management']) && $this->looksLikeManagementPayload($decoded)) {
            $copy = $decoded;
            unset($copy['ok'], $copy['signature'], $copy['alg'], $copy['message'], $copy['meta'], $copy['connection'], $copy['binding']);
            $out['management'] = $copy;
        }

        foreach (['checkout', 'session', 'data'] as $key) {
            if ($kind !== 'catalog' && isset($decoded[$key]) && is_array($decoded[$key])) {
                $out['checkout'] = $decoded[$key];
                break;
            }
        }

        if ($kind === 'catalog' && empty($out['catalog'])) {
            $copy = $decoded;
            unset($copy['ok'], $copy['signature'], $copy['alg'], $copy['message'], $copy['meta'], $copy['connection'], $copy['binding']);
            if (is_array($copy)) {
                $out['catalog'] = $copy;
            }
        }

        return $out;
    }

    /**
     * @param array<string,mixed> $payload
     */
    private function looksLikeManagementPayload(array $payload): bool
    {
        foreach (['customer', 'subscription', 'payment_methods', 'available_operations', 'allowed_operations', 'plans', 'actions', 'site_assignments', 'usage', 'usage_summary', 'seat_assignments', 'usage_events'] as $key) {
            if (array_key_exists($key, $payload)) {
                return true;
            }
        }
        return false;
    }

    /**
     * @return array<string,mixed>
     */
    private function basePayload(string $licenseKey): array
    {
        $licenseKey = trim($licenseKey);
        if ($licenseKey === '') {
            return [];
        }

        return [
            'license_key' => $licenseKey,
        ];
    }

    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;
    }
}
