<?php

declare(strict_types=1);

namespace MauticPlugin\DealFlowBundle\Service;

use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use Mautic\CoreBundle\Helper\CoreParametersHelper;
use Psr\Cache\CacheItemPoolInterface;
use Psr\Log\LoggerInterface;

/**
 * UpdateService checks whether a newer version of the Deal Flow plugin is available.
 *
 * It does NOT download, extract, or swap files. All file operations are performed
 * from the CLI via `bin/console dealflow:update`.
 */
final class UpdateService implements UpdateServiceInterface
{
    // v2 suffix: cached payload shape changed in 0.9.4 (added releasedAt) — a new
    // key prevents serving pre-0.9.4 entries that would hide the release date for a TTL.
    private const CACHE_KEY = 'dealflow_update_check_v2';
    private const CACHE_TTL = 86400; // 24 hours
    private const API_CONNECT_TIMEOUT = 5;
    private const API_READ_TIMEOUT = 15;

    private ?Client $httpClient = null;

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

    public function checkForUpdates(bool $forceRefresh = false): UpdateCheckResult
    {
        $currentVersion = $this->getCurrentVersion();

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

        $key = $this->licenseService->getCurrentKey();
        if ($key === null) {
            return new UpdateCheckResult(
                updateAvailable: false,
                latestVersion: $currentVersion,
                currentVersion: $currentVersion,
                error: true,
                errorMessage: 'No license key configured',
            );
        }

        $subdomain = $this->licenseService->getSubdomain();
        if ($subdomain === null) {
            return new UpdateCheckResult(
                updateAvailable: false,
                latestVersion: $currentVersion,
                currentVersion: $currentVersion,
                error: true,
                errorMessage: 'Could not determine instance hostname',
            );
        }

        $apiUrl = $this->getApiUrl();
        if ($apiUrl === null) {
            return new UpdateCheckResult(
                updateAvailable: false,
                latestVersion: $currentVersion,
                currentVersion: $currentVersion,
                error: true,
                errorMessage: 'License API URL not configured or invalid',
            );
        }

        try {
            $response = $this->getHttpClient()->get($apiUrl.'/api/updates/check', [
                'query' => [
                    'key' => $key,
                    'subdomain' => $subdomain,
                    'current_version' => $currentVersion,
                ],
                'connect_timeout' => self::API_CONNECT_TIMEOUT,
                'timeout' => self::API_READ_TIMEOUT,
                'http_errors' => false,
            ]);

            $statusCode = $response->getStatusCode();
            $body = json_decode((string) $response->getBody(), true);

            if ($statusCode === 403) {
                $message = (string) ($body['error'] ?? 'License validation failed');

                return new UpdateCheckResult(
                    updateAvailable: false,
                    latestVersion: $currentVersion,
                    currentVersion: $currentVersion,
                    error: true,
                    errorMessage: $message,
                );
            }

            if ($statusCode !== 200 || !\is_array($body)) {
                return new UpdateCheckResult(
                    updateAvailable: false,
                    latestVersion: $currentVersion,
                    currentVersion: $currentVersion,
                    error: true,
                    errorMessage: 'Unexpected response from update server (HTTP '.$statusCode.')',
                );
            }

            $result = new UpdateCheckResult(
                updateAvailable: (bool) ($body['update_available'] ?? false),
                latestVersion: (string) ($body['latest_version'] ?? $currentVersion),
                currentVersion: $currentVersion,
                downloadUrl: isset($body['download_url']) ? (string) $body['download_url'] : null,
                changelog: isset($body['changelog']) ? (string) $body['changelog'] : null,
                sha256: isset($body['sha256']) ? (string) $body['sha256'] : null,
                requiresMigration: (bool) ($body['requires_migration'] ?? false),
                eligible: (bool) ($body['eligible'] ?? true),
                updatesUntil: isset($body['updates_until']) ? (string) $body['updates_until'] : null,
                releasedAt: isset($body['released_at']) ? (string) $body['released_at'] : null,
            );

            $this->cacheResult($result);
            $this->logger->info('Deal Flow update check: '.($result->updateAvailable ? 'update available ('.$result->latestVersion.')' : 'up to date'));

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

            return new UpdateCheckResult(
                updateAvailable: false,
                latestVersion: $currentVersion,
                currentVersion: $currentVersion,
                error: true,
                errorMessage: 'Could not reach update server - please try again later',
            );
        }
    }

    public function getCurrentVersion(): string
    {
        $configPath = \dirname(__DIR__).'/Config/config.php';
        if (!file_exists($configPath)) {
            return '0.0.0';
        }

        $config = require $configPath;
        if (\is_array($config) && isset($config['version'])) {
            return (string) $config['version'];
        }

        return '0.0.0';
    }

    public function clearUpdateCache(): void
    {
        try {
            $this->cache->deleteItem(self::CACHE_KEY);
        } catch (\Throwable $e) {
            $this->logger->debug('Deal Flow: failed to clear update cache: '.$e->getMessage());
        }
    }

    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(UpdateCheckResult $result): void
    {
        try {
            $cacheItem = $this->cache->getItem(self::CACHE_KEY);
            $cacheItem->set([
                'updateAvailable' => $result->updateAvailable,
                'latestVersion' => $result->latestVersion,
                'currentVersion' => $result->currentVersion,
                'downloadUrl' => $result->downloadUrl,
                'changelog' => $result->changelog,
                'sha256' => $result->sha256,
                'requiresMigration' => $result->requiresMigration,
                'eligible' => $result->eligible,
                'updatesUntil' => $result->updatesUntil,
                'releasedAt' => $result->releasedAt,
            ]);
            $cacheItem->expiresAfter(self::CACHE_TTL);
            $this->cache->save($cacheItem);
        } catch (\Throwable $e) {
            $this->logger->debug('Deal Flow: failed to cache update check result: '.$e->getMessage());
        }
    }

    private function getCachedResult(string $currentVersion): ?UpdateCheckResult
    {
        try {
            $cacheItem = $this->cache->getItem(self::CACHE_KEY);
            if ($cacheItem->isHit()) {
                $data = $cacheItem->get();
                if (\is_array($data) && \array_key_exists('updateAvailable', $data)) {
                    // Invalidate cache if current version changed (e.g., manual update)
                    if (isset($data['currentVersion']) && $data['currentVersion'] !== $currentVersion) {
                        $this->cache->deleteItem(self::CACHE_KEY);

                        return null;
                    }

                    return new UpdateCheckResult(
                        updateAvailable: (bool) $data['updateAvailable'],
                        latestVersion: (string) ($data['latestVersion'] ?? $currentVersion),
                        currentVersion: $currentVersion,
                        downloadUrl: isset($data['downloadUrl']) ? (string) $data['downloadUrl'] : null,
                        changelog: isset($data['changelog']) ? (string) $data['changelog'] : null,
                        sha256: isset($data['sha256']) ? (string) $data['sha256'] : null,
                        requiresMigration: (bool) ($data['requiresMigration'] ?? false),
                        eligible: (bool) ($data['eligible'] ?? true),
                        updatesUntil: isset($data['updatesUntil']) ? (string) $data['updatesUntil'] : null,
                        releasedAt: isset($data['releasedAt']) ? (string) $data['releasedAt'] : null,
                        fromCache: true,
                    );
                }
            }
        } catch (\Throwable $e) {
            $this->logger->debug('Deal Flow: update cache read failed: '.$e->getMessage());
        }

        return null;
    }
}
