<?php

declare(strict_types=1);

namespace ReleaseInsights;

class Data
{
    /** @var array<string, string> $future_releases */
    private readonly array $future_releases;

    /** @var array<string, string> $release_owners */
    private(set) array $release_owners;

    /** @var array<string> $wellness_days */
    private(set) array $wellness_days;

    /** @var array<string> $chemspills */
    private(set) array $chemspills;

    public function __construct(
        private readonly string $pd_url = URL::ProductDetails->value,
        public int $cache_duration = 900 // 15 minutes
    ) {
        $this->release_owners  = include DATA . 'release_owners.php';
        $this->future_releases = include DATA . 'upcoming_releases.php';
        $this->wellness_days   = include DATA . 'wellness_days.php';
        $this->chemspills      = include DATA . 'chemspill_releases.php';
    }

    /** @return array<string, string> */
    public function getFutureReleases(): array
    {
        return array_filter(
            $this->future_releases,
            fn(string $key) => (int) $key > RELEASE,
            ARRAY_FILTER_USE_KEY
        );
    }

    /** @return array<string, string> */
    public function getESRReleases(): array
    {
        // Historical data from Product Details, cache a week
        $esr_releases = Json::load($this->pd_url . 'firefox.json', $this->cache_duration)['releases'];

        // Reduce to only ESR releases
        $esr_releases = array_filter(
            $esr_releases,
            fn(string $key) => str_ends_with($key, 'esr'),
            ARRAY_FILTER_USE_KEY
        );

        // Rebuild a version_number => date array
        $esr_releases = array_column($esr_releases, 'date', 'version');

        // Sort releases by release date
        asort($esr_releases);

        return $esr_releases;
    }

    /**
     * Get the release date of our Latest Major release
     *
     * @return array<string, string>
     */
    public function getLatestMajorRelease(): array
    {
        $past_releases = $this->getMajorPastReleases();
        $last_release = array_key_last($past_releases);

        return [$last_release => $past_releases[$last_release]];
    }

    /**
     * Get all past Desktop Releases on the release channel,
     *
     * @return array<string, string>
     */
    public function getDesktopPastReleases(bool $dot_releases = true): array
    {
        // Historical data from Product Details, cache a week
        $major_releases = Json::load($this->pd_url . 'firefox_history_major_releases.json', $this->cache_duration);
        $minor_releases =  $dot_releases == true ? Json::load($this->pd_url . 'firefox_history_stability_releases.json', $this->cache_duration) : [];
        $all_releases = [...$major_releases, ...$minor_releases];

        // Sort releases by release date
        asort($all_releases);

        // Remove all minor ESR releases
        $exclude_esr = function (string $version_number) {
            // Those releases were not ESR releases despite the middle number
            if (in_array($version_number, ['33.1', '33.1.1', '50.1.0'])) {
                return true;
            }

            $data = explode('.', $version_number);

            // We started ESR releases with version 10
            if (intval($data[0]) < 10) {
                return true;
            }

            if (intval($data[1]) > 0) {
                return false;
            }

            return true;
        };

        return array_filter($all_releases, $exclude_esr, ARRAY_FILTER_USE_KEY);
    }

    /**
     * Get all Dot Releases for both Desktop and Android
     * We ignore Android dot releases before 126
     *
     * @return array<string, array<string,string>>
     */
    public function getDotReleases(): array
    {
        $filter = function(string $platform) {
            $target = $platform == 'desktop' ? 'firefox.json' : 'mobile_android.json';

            // Get source of Data where we can extract dot releases information
            $data = Json::load($this->pd_url . $target, $this->cache_duration)['releases'];

            // Extract all minor releases
            $data = array_filter($data, fn($v) => isset($v['category']) && $v['category'] == 'stability');

            if ($platform === 'desktop') {
               // Filter out ESR releases
                $data = array_filter($data, fn($k) => ! str_ends_with($k, 'esr'), ARRAY_FILTER_USE_KEY);
            }

            // Rebuild a simplified array: ['128.0.1' => '2024-07-16',...]
            $data = array_column($data, 'date', 'version');

            if ($platform === 'android') {
                // Filter out versions older than 126.
                // 126 is when we merged android and desktop code and aligned dot release naming.
                $data = array_filter($data, fn($k) => explode('.', $k)[0] > 125, ARRAY_FILTER_USE_KEY);
            }

            return $data;
        };

        $desktop = $filter('desktop');
        $android = $filter('android');

        $all = array_merge($desktop, $android);
        ksort ($all, SORT_NATURAL);

        $dot_releases = [];
        foreach ($all as $version => $date) {
            $platform = 'both';

            if (! array_key_exists($version, $android)) {
                $platform = 'desktop';
            }

            if (! array_key_exists($version, $desktop)) {
                $platform = 'android';
            }

            $dot_releases[$version] = ['date' => $date, 'platform' => $platform];
        }

        return $dot_releases;
    }

    /**
     * Get all past Betas
     *
     * @return array<string, string>
     */
    public function getPastBetas(): array
    {
        return Json::load($this->pd_url . 'firefox_history_development_releases.json', $this->cache_duration);
    }

    /**
     * Get all past Releases on the release channel, but not dot releases
     *
     * @return array<string, string>
     */
    public function getMajorPastReleases(): array
    {
        return Json::load($this->pd_url . 'firefox_history_major_releases.json', $this->cache_duration);
    }

    /**
     * Get all past and planned Releases on the release channel, but not dot releases
     *
     * @return array<string, string>
     */
    public function getMajorReleases(): array
    {
        return [
            ...$this->getMajorPastReleases(),
            ...$this->future_releases,
        ];
    }

    /** @return array<string, string> */
    public function getFirefoxVersions(): array
    {
        // Cache Product Details versions, 15mn cache
        return Json::load($this->pd_url . 'firefox_versions.json', $this->cache_duration);
    }

    public static function getDesktopAdoptionRate(string $version): ?float
    {
        // Check current uptake rate for the latest release*
        // @codeCoverageIgnoreStart
        if (! defined('TESTING_CONTEXT')) {
            $uptake = Json::load(URL::Pollbot->value
                    . 'firefox/'
                    . $version
                    . '/telemetry/main-summary-uptake')['message'] ?? '0' ;
        } else {
        // @codeCoverageIgnoreEnd
            if ($version == '130.0') {
                $uptake = Json::load(URL::Pollbot->target() . 'main-summary-uptake.json')['message'] ?? '0' ;
            } else {
                $uptake = 'Query results contained no rows.';
            }
        }

        if ($uptake == 'Query results contained no rows.') {
            return null;
        }

        // This public data is stored as a string, extract only the number
        $uptake = preg_replace('/Telemetry uptake for version.*\(.*\) is /', '', $uptake);
        $uptake = str_replace('%', '', $uptake);

        return  (float) $uptake;
    }

    /**
     * On Release day we have a lot of special cases.
     */
    public function isTodayReleaseDay(): bool
    {
        return in_array(date('Y-m-d'), $this->getMajorReleases());
    }
}
