<?php

namespace WPSN\Modules\Licensing;

use WPSN\Core\Logger;
use WPSN\Core\Settings;

defined('ABSPATH') || exit;

final class BillingSyncService
{
    private Settings $settings;
    private Logger $logger;

    private LicenseSecretsRepository $secrets;
    private BillingSyncCacheRepository $cache;
    private BillingSyncClient $client;
    private LicenseSignatureVerifier $sig;

    public function __construct(Settings $settings, Logger $logger)
    {
        $this->settings = $settings;
        $this->logger = $logger;
        $this->secrets = new LicenseSecretsRepository();
        $this->cache = new BillingSyncCacheRepository();
        $this->client = new BillingSyncClient($settings, $logger);
        $this->sig = new LicenseSignatureVerifier($settings);
    }

    public function isEnabled(): bool
    {
        return (bool) $this->settings->get('licensing.billing_sync_enabled', false);
    }

    public function hasLicenseKey(): bool
    {
        return $this->secrets->hasLicenseKey();
    }

    public function serverUrl(): string
    {
        return trim((string) $this->settings->get('licensing.server_url', ''));
    }

    public function cacheTtlSeconds(): int
    {
        $hours = (int) $this->settings->get('licensing.billing_sync_cache_ttl_hours', 12);
        $hours = max(1, min(168, $hours));
        return $hours * HOUR_IN_SECONDS;
    }

    public function scheduled(): bool
    {
        return (bool) $this->settings->get('licensing.billing_sync_scheduled', true);
    }

    public function requireSigned(): bool
    {
        return (bool) $this->settings->get('licensing.billing_sync_require_signed', false);
    }

    /**
     * @return array<string,mixed>|null
     */
    public function getCache(): ?array
    {
        $cache = $this->cache->get();
        return is_array($cache) ? $cache : null;
    }

    public function clearCache(): void
    {
        $this->cache->clear();
    }

    /**
     * @return array{ok:bool,verified?:bool,error?:string,retry_after?:int,trusted?:bool}
     */
    public function syncIfNeeded(bool $force = false): array
    {
        if (!$this->isEnabled()) {
            return ['ok' => true, 'verified' => false];
        }

        if (!$this->hasLicenseKey()) {
            return ['ok' => false, 'verified' => false, 'error' => __('License key is empty.', 'wp-seo-next')];
        }

        if ($this->serverUrl() === '') {
            return ['ok' => false, 'verified' => false, 'error' => __('License server URL is not configured.', 'wp-seo-next')];
        }

        $cache = $this->getCache();
        $checkedAt = $cache && isset($cache['checked_at']) ? (int) $cache['checked_at'] : 0;
        if (!$force && $checkedAt > 0 && (time() - $checkedAt) < $this->cacheTtlSeconds()) {
            return ['ok' => true, 'verified' => false, 'trusted' => !empty($cache['trusted'])];
        }

        $res = $this->client->fetch($this->secrets->getLicenseKey());
        $now = time();

        if (empty($res['ok'])) {
            $error = isset($res['error']) ? (string) $res['error'] : __('Billing sync failed.', 'wp-seo-next');
            $retryAfter = isset($res['retry_after']) ? (int) $res['retry_after'] : 0;

            $newCache = is_array($cache) ? $cache : [];
            $newCache['checked_at'] = $now;
            $newCache['last_error'] = $error;
            $newCache['retry_after'] = $retryAfter;
            $newCache['status'] = 'failed';
            $meta = isset($res['meta']) && is_array($res['meta']) ? $res['meta'] : [];
            if (!empty($meta)) {
                $newCache['server'] = isset($newCache['server']) && is_array($newCache['server']) ? $newCache['server'] : [];
                $newCache['server']['meta'] = $meta;
            }
            $this->cache->set($newCache);

            do_action('wpsn/licensing/billing_sync_failed', $error, $retryAfter);

            return ['ok' => false, 'verified' => false, 'error' => $error, 'retry_after' => $retryAfter];
        }

        $billing = isset($res['billing']) && is_array($res['billing']) ? $res['billing'] : [];
        $signature = isset($res['signature']) ? (string) $res['signature'] : '';
        $alg = isset($res['alg']) ? (string) $res['alg'] : '';
        $kid = isset($res['kid']) ? (string) $res['kid'] : '';
        $meta = isset($res['meta']) && is_array($res['meta']) ? $res['meta'] : [];
        $connection = isset($res['connection']) && is_array($res['connection']) ? $res['connection'] : [];

        $trusted = false;
        if ($signature !== '') {
            $trusted = $this->sig->verify($billing, $signature, $alg, $kid);
        }
        $trusted = (bool) apply_filters('wpsn/licensing/billing_sync/trusted', $trusted, $billing, $signature, $alg, $kid, $meta);
        $this->persistConnection($connection, $trusted);

        if ($this->requireSigned() && !$trusted) {
            $error = __('Billing payload is unsigned or untrusted.', 'wp-seo-next');
            $newCache = is_array($cache) ? $cache : [];
            $newCache['checked_at'] = $now;
            $newCache['last_error'] = $error;
            $newCache['retry_after'] = 0;
            $newCache['status'] = 'untrusted';
            $newCache['server'] = [
                'meta' => $meta,
                'connection' => $this->cacheableConnection($connection),
            ];
            $this->cache->set($newCache);

            do_action('wpsn/licensing/billing_sync_failed', $error, 0);

            return ['ok' => false, 'verified' => false, 'error' => $error, 'trusted' => false];
        }

        $newCache = [
            'billing' => $billing,
            'signature' => $signature,
            'alg' => $alg,
            'trusted' => $trusted,
            'status' => 'ok',
            'checked_at' => $now,
            'last_error' => '',
            'retry_after' => 0,
            'last_success_at' => $now,
            'server' => [
                'meta' => $meta,
                'connection' => $this->cacheableConnection($connection),
            ],
        ];
        $this->cache->set($newCache);

        do_action('wpsn/licensing/billing_synced', $billing, $trusted);

        return ['ok' => true, 'verified' => true, 'trusted' => $trusted];
    }

    /**
     * @param array<string,string> $connection
     */
    private function persistConnection(array $connection, bool $trusted): void
    {
        if (empty($connection)) {
            return;
        }

        if (!$trusted) {
            unset($connection['request_secret']);
        }

        if (empty($connection)) {
            return;
        }

        $this->secrets->updateServerBinding($connection);
    }

    /**
     * @param array<string,string> $connection
     * @return array<string,string>
     */
    private function cacheableConnection(array $connection): array
    {
        unset($connection['request_secret']);
        return $connection;
    }
}
