<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\Service;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Mautic\PluginBundle\Helper\IntegrationHelper;
use MauticPlugin\DealFlowBundle\Integration\DealFlowIntegration;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerInterface;

final class LicenseService
{
    private const CACHE_KEY = 'dealflow_license_validation';
    private const CACHE_TTL = 86400;
    private const API_CONNECT_TIMEOUT = 5;
    private const API_READ_TIMEOUT = 10;
    private const KEY_REGEX = '/^DF-[A-Z2-9]{4}-[A-Z2-9]{4}-[A-Z2-9]{4}-[A-Z2-9]{4}$/';

    private ?Client $httpClient = null;
    private ?IntegrityService $integrityService = null;

    /** @var array<string, mixed>|null */
    private ?array $integrationKeys = null;

    /** @var array<string, mixed>|null */
    private ?array $integrationSettings = null;

    public function __construct(
        private readonly CoreParametersHelper $params,
        private readonly CacheItemPoolInterface $cache,
        private readonly LoggerInterface $logger,
        private readonly IntegrationHelper $integrationHelper,
    ) {
    }

    /**
     * Inject the IntegrityService (set via methodCall in config.php to avoid circular deps).
     */
    public function setIntegrityService(IntegrityService $integrityService): void
    {
        $this->integrityService = $integrityService;
    }

    /**
     * Check whether the plugin is genuinely licensed.
     *
     * This verifies BOTH the config status flag AND the integrity hash.
     * All enforcement code should use this method instead of getStatus() === 'active'.
     */
    public function isLicensed(): bool
    {
        if ($this->getStatus() !== 'active') {
            return false;
        }

        // If IntegrityService is not available (e.g. during container build), fall back to status-only check
        if ($this->integrityService === null) {
            return true;
        }

        $key = $this->getCurrentKey();
        $subdomain = $this->getSubdomain();
        $this->loadIntegrationData();
        $hash = (string) ($this->integrationSettings['license_hash']
            ?? $this->params->get('dealflow_license_hash', ''));

        if ($key === null || $subdomain === null || $hash === '') {
            $this->logger->debug('Deal Flow: license integrity check failed — missing key, subdomain, or hash.');

            return false;
        }

        return $this->integrityService->verifyLicenseIntegrity($key, $subdomain, $hash);
    }

    public function activate(string $key): LicenseResult
    {
        $subdomain = $this->getSubdomain();
        if ($subdomain === null) {
            return new LicenseResult(false, 'Could not determine instance hostname');
        }

        $apiUrl = $this->getApiUrl();
        if ($apiUrl === null) {
            return new LicenseResult(false, 'License API URL not configured or invalid');
        }

        try {
            $response = $this->getHttpClient()->post($apiUrl.'/api/license/activate', [
                'json' => ['key' => $key, 'subdomain' => $subdomain],
                'connect_timeout' => self::API_CONNECT_TIMEOUT,
                'timeout' => self::API_READ_TIMEOUT,
                'http_errors' => false,
            ]);

            $body = json_decode((string) $response->getBody(), true);
            $valid = (bool) ($body['valid'] ?? false);
            $message = (string) ($body['message'] ?? 'Unknown response from license server');
            $updatesUntil = isset($body['updates_until']) ? (string) $body['updates_until'] : null;

            $result = new LicenseResult($valid, $message, $updatesUntil);

            if ($result->valid) {
                $this->cacheResult($result);
            }

            $this->logger->info('Deal Flow license activation: '.substr($key, 0, 7).'... -> '.($valid ? 'success' : 'failed: '.$message));

            return $result;
        } catch (GuzzleException $e) {
            $this->logger->info('Deal Flow license activation failed (API unreachable): '.$e->getMessage());

            return new LicenseResult(false, 'Could not reach license server - please try again later', null, false, true);
        }
    }

    public function validate(): LicenseResult
    {
        $key = $this->getCurrentKey();
        if ($key === null) {
            return new LicenseResult(false, 'No license key configured');
        }

        $cached = $this->getCachedResult();
        if ($cached !== null) {
            return $cached;
        }

        $subdomain = $this->getSubdomain();
        if ($subdomain === null) {
            return new LicenseResult(false, 'Could not determine instance hostname');
        }

        $apiUrl = $this->getApiUrl();
        if ($apiUrl === null) {
            return $this->getFallbackResult();
        }

        try {
            $response = $this->getHttpClient()->post($apiUrl.'/api/license/validate', [
                'json' => ['key' => $key, 'subdomain' => $subdomain],
                'connect_timeout' => self::API_CONNECT_TIMEOUT,
                'timeout' => self::API_READ_TIMEOUT,
                'http_errors' => false,
            ]);

            $body = json_decode((string) $response->getBody(), true);
            $valid = (bool) ($body['valid'] ?? false);
            $message = (string) ($body['message'] ?? 'Unknown response');
            $updatesUntil = isset($body['updates_until']) ? (string) $body['updates_until'] : null;

            $result = new LicenseResult($valid, $message, $updatesUntil);
            $this->cacheResult($result);

            $this->logger->debug('Deal Flow license validation: '.($valid ? 'valid' : 'invalid: '.$message));

            return $result;
        } catch (GuzzleException $e) {
            $this->logger->debug('Deal Flow license validation failed (API unreachable): '.$e->getMessage());

            return $this->getFallbackResult();
        }
    }

    public function getStatus(): string
    {
        $this->loadIntegrationData();
        $status = $this->integrationSettings['license_status'] ?? null;
        if (\is_string($status) && $status !== '') {
            return $status;
        }

        // Fallback to config params (legacy installs)
        // @deprecated since v0.9.0 — license data should be stored in IntegrationHelper. Removal target: v1.0.0.
        $this->logger->notice('[DealFlow] DEPRECATED: Reading license_status from core config params (legacy install fallback). Migrate to integration settings. Removal target: v1.0.0');

        return (string) $this->params->get('dealflow_license_status', 'not_configured');
    }

    public function getCurrentKey(): ?string
    {
        $this->loadIntegrationData();
        $key = $this->integrationKeys['license_key'] ?? '';
        if (\is_string($key) && $key !== '') {
            return $key;
        }

        // Fallback to config params (legacy installs)
        // @deprecated since v0.9.0 — license data should be stored in IntegrationHelper. Removal target: v1.0.0.
        $this->logger->notice('[DealFlow] DEPRECATED: Reading license_key from core config params (legacy install fallback). Migrate to integration settings. Removal target: v1.0.0');
        $key = (string) $this->params->get('dealflow_license_key', '');

        return $key !== '' ? $key : null;
    }

    /**
     * @return array{status: string, label: string, color: string, updates_until: string|null}
     */
    public function getDisplayStatus(): array
    {
        $status = $this->getStatus();
        $this->loadIntegrationData();
        $updatesUntil = (string) ($this->integrationSettings['license_updates_until']
            ?? $this->params->get('dealflow_license_updates_until', ''));

        return match ($status) {
            'active' => $this->buildActiveDisplayStatus($updatesUntil !== '' ? $updatesUntil : null),
            'invalid' => [
                'status' => 'invalid',
                'label' => 'Invalid or expired - check your key',
                'color' => 'danger',
                'updates_until' => null,
            ],
            'pending' => [
                'status' => 'pending',
                'label' => 'Pending activation - save settings to activate',
                'color' => 'warning',
                'updates_until' => null,
            ],
            default => [
                'status' => 'not_configured',
                'label' => 'No license configured - plugin running in evaluation mode',
                'color' => 'default',
                'updates_until' => null,
            ],
        };
    }

    public function getSubdomain(): ?string
    {
        // Allow explicit override via config (useful for test instances where site_url is localhost)
        $override = (string) $this->params->get('dealflow_subdomain', '');
        if ($override !== '') {
            return strtolower($override);
        }

        $siteUrl = (string) $this->params->get('site_url', '');
        if ($siteUrl === '') {
            return null;
        }

        $host = parse_url(rtrim($siteUrl, '/'), PHP_URL_HOST);
        if (!\is_string($host) || $host === '' || $host === 'localhost') {
            return null;
        }

        return strtolower($host);
    }

    public function maskKey(string $key): string
    {
        if (!preg_match(self::KEY_REGEX, $key)) {
            return $key;
        }

        $parts = explode('-', $key);

        return $parts[0].'-'.$parts[1].'-****-****-'.$parts[4];
    }

    public function clearValidationCache(): void
    {
        $this->cache->deleteItem(self::CACHE_KEY);
    }

    public static function isValidKeyFormat(string $key): bool
    {
        return (bool) preg_match(self::KEY_REGEX, $key);
    }

    private function getApiUrl(): ?string
    {
        $url = (string) $this->params->get(
            'dealflow_license_api_url',
            'https://deal-flow-license-api-700460720657.us-east1.run.app'
        );

        if (!str_starts_with($url, 'https://')) {
            return null;
        }

        return rtrim($url, '/');
    }

    private function getHttpClient(): Client
    {
        if ($this->httpClient === null) {
            $this->httpClient = new Client();
        }

        return $this->httpClient;
    }

    private function cacheResult(LicenseResult $result): void
    {
        try {
            $cacheItem = $this->cache->getItem(self::CACHE_KEY);
            $cacheItem->set([
                'valid' => $result->valid,
                'message' => $result->message,
                'updatesUntil' => $result->updatesUntil,
            ]);
            $cacheItem->expiresAfter(self::CACHE_TTL);
            $this->cache->save($cacheItem);
        } catch (\Throwable $e) {
            $this->logger->debug('Deal Flow: failed to cache license result: '.$e->getMessage());
        }
    }

    private function getCachedResult(): ?LicenseResult
    {
        try {
            $cacheItem = $this->cache->getItem(self::CACHE_KEY);
            if ($cacheItem->isHit()) {
                $data = $cacheItem->get();
                if (\is_array($data) && \array_key_exists('valid', $data)) {
                    return new LicenseResult(
                        (bool) $data['valid'],
                        (string) ($data['message'] ?? ''),
                        isset($data['updatesUntil']) ? (string) $data['updatesUntil'] : null,
                        true,
                    );
                }
            }
        } catch (\Throwable $e) {
            $this->logger->debug('Deal Flow: cache read failed: '.$e->getMessage());
        }

        return null;
    }

    private function getFallbackResult(): LicenseResult
    {
        $this->loadIntegrationData();
        $lastKnownValid = $this->integrationSettings['license_last_validation_valid']
            ?? $this->params->get('dealflow_license_last_validation_valid');

        if ($lastKnownValid === null || $lastKnownValid === '' || $lastKnownValid === false) {
            return new LicenseResult(true, 'License not yet validated (API unreachable)', null, false, true);
        }

        $updatesUntil = (string) ($this->integrationSettings['license_updates_until']
            ?? $this->params->get('dealflow_license_updates_until', ''));

        return new LicenseResult(
            (bool) $lastKnownValid,
            'Using last known validation result (API unreachable)',
            $updatesUntil !== '' ? $updatesUntil : null,
            false,
            true,
        );
    }

    /**
     * Load integration keys and feature settings from the plugin_integration_settings entity.
     * Caches the result for the duration of the request.
     */
    private function loadIntegrationData(): void
    {
        if ($this->integrationKeys !== null) {
            return;
        }

        $this->integrationKeys = [];
        $this->integrationSettings = [];

        try {
            $integration = $this->integrationHelper->getIntegrationObject('DealFlow');
            if ($integration instanceof DealFlowIntegration) {
                $this->integrationKeys = $integration->getKeys() ?? [];
                $entity = $integration->getIntegrationSettings();
                $this->integrationSettings = $entity->getFeatureSettings() ?? [];
            }
        } catch (\Throwable $e) {
            $this->logger->debug('Deal Flow: could not load integration data: '.$e->getMessage());
        }
    }

    /**
     * @return array{status: string, label: string, color: string, updates_until: string|null}
     */
    private function buildActiveDisplayStatus(?string $updatesUntil): array
    {
        if ($updatesUntil !== null) {
            try {
                $until = new \DateTimeImmutable($updatesUntil);
                $now = new \DateTimeImmutable();

                if ($until > $now) {
                    return [
                        'status' => 'active',
                        'label' => 'Active - updates until '.$until->format('Y-m-d'),
                        'color' => 'success',
                        'updates_until' => $updatesUntil,
                    ];
                }

                return [
                    'status' => 'active_expired',
                    'label' => 'Active (updates expired '.$until->format('Y-m-d').') - renewal available',
                    'color' => 'warning',
                    'updates_until' => $updatesUntil,
                ];
            } catch (\Exception) {
                // Invalid date, fall through
            }
        }

        return [
            'status' => 'active',
            'label' => 'Active',
            'color' => 'success',
            'updates_until' => null,
        ];
    }
}
