<?php

app_require('routes/moduleWeatherAPIRoute.php');

use routes\moduleWeatherAPIRoute;

function weather_cache_invoke_private(moduleWeatherAPIRoute $route, string $method, array $args = []): mixed
{
    $reflection = new ReflectionClass($route);
    $target = $reflection->getMethod($method);

    return $target->invokeArgs($route, $args);
}

beforeEach(function (): void {
    $_SERVER['REQUEST_URI'] = '/departments/weather';
    $this->oldTtl = getenv('DEPARTMENTS_WEATHER_CACHE_TTL');
    $this->oldStaleTtl = getenv('DEPARTMENTS_WEATHER_STALE_TTL');
    $this->oldHotTtl = getenv('DEPARTMENTS_WEATHER_PRELOAD_HOT_TTL');
});

afterEach(function (): void {
    if ($this->oldTtl === false) {
        putenv('DEPARTMENTS_WEATHER_CACHE_TTL');
    } else {
        putenv('DEPARTMENTS_WEATHER_CACHE_TTL=' . $this->oldTtl);
    }

    if ($this->oldStaleTtl === false) {
        putenv('DEPARTMENTS_WEATHER_STALE_TTL');
    } else {
        putenv('DEPARTMENTS_WEATHER_STALE_TTL=' . $this->oldStaleTtl);
    }

    if ($this->oldHotTtl === false) {
        putenv('DEPARTMENTS_WEATHER_PRELOAD_HOT_TTL');
    } else {
        putenv('DEPARTMENTS_WEATHER_PRELOAD_HOT_TTL=' . $this->oldHotTtl);
    }
});

it('uses sane ttl defaults and clamps negative ttl to zero', function (): void {
    $route = new moduleWeatherAPIRoute();

    putenv('DEPARTMENTS_WEATHER_CACHE_TTL');
    expect(weather_cache_invoke_private($route, 'getDepartmentWeatherCacheTtl'))->toBe(60);

    putenv('DEPARTMENTS_WEATHER_CACHE_TTL=45');
    expect(weather_cache_invoke_private($route, 'getDepartmentWeatherCacheTtl'))->toBe(45);

    putenv('DEPARTMENTS_WEATHER_CACHE_TTL=-10');
    expect(weather_cache_invoke_private($route, 'getDepartmentWeatherCacheTtl'))->toBe(0);
});

it('uses sane stale ttl defaults and clamps stale ttl below fresh ttl', function (): void {
    $route = new moduleWeatherAPIRoute();

    putenv('DEPARTMENTS_WEATHER_STALE_TTL');
    expect(weather_cache_invoke_private($route, 'getDepartmentWeatherStaleCacheTtl', [60]))->toBe(300);

    putenv('DEPARTMENTS_WEATHER_STALE_TTL=10');
    expect(weather_cache_invoke_private($route, 'getDepartmentWeatherStaleCacheTtl', [60]))->toBe(60);

    putenv('DEPARTMENTS_WEATHER_STALE_TTL=900');
    expect(weather_cache_invoke_private($route, 'getDepartmentWeatherStaleCacheTtl', [60]))->toBe(900);
});

it('uses sane hot activity ttl defaults and clamps to a positive value', function (): void {
    $route = new moduleWeatherAPIRoute();

    putenv('DEPARTMENTS_WEATHER_PRELOAD_HOT_TTL');
    expect(weather_cache_invoke_private($route, 'getDepartmentWeatherHotActivityTtl'))->toBe(900);

    putenv('DEPARTMENTS_WEATHER_PRELOAD_HOT_TTL=-5');
    expect(weather_cache_invoke_private($route, 'getDepartmentWeatherHotActivityTtl'))->toBe(1);
});

it('builds deterministic cache keys for department sets and timeline ranges', function (): void {
    $route = new moduleWeatherAPIRoute();
    $range = [
        'start' => new DateTime('2026-03-24 00:00:00'),
        'endExclusive' => new DateTime('2026-03-25 00:00:00'),
    ];
    $differentRange = [
        'start' => new DateTime('2026-03-24 00:00:00'),
        'endExclusive' => new DateTime('2026-03-26 00:00:00'),
    ];

    $keyA = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[1, 3, 5], $range]);
    $keyB = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[1, 3, 5], $range]);
    $keyC = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[1, 3, 5], $differentRange]);

    expect($keyA)->toBe($keyB);
    expect($keyA)->not->toBe($keyC);
    expect($keyA)->toStartWith('departments_weather:timeline:v2:');
});

it('builds order-insensitive cache keys for equivalent department id sets', function (): void {
    $route = new moduleWeatherAPIRoute();
    $range = [
        'start' => new DateTime('2026-03-24 00:00:00'),
        'endExclusive' => new DateTime('2026-03-25 00:00:00'),
    ];

    $ordered = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[1, 3, 5], $range]);
    $shuffled = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[5, 1, 3], $range]);

    expect($ordered)->toBe($shuffled);
});

it('changes weather timeline cache key when department weather targets change', function (): void {
    $route = new moduleWeatherAPIRoute();
    $range = [
        'start' => new DateTime('2026-03-24 00:00:00'),
        'endExclusive' => new DateTime('2026-03-25 00:00:00'),
    ];

    $baseTargets = [
        1 => ['degraded_threshold' => 1.0, 'healthy_threshold' => 1.3],
        3 => ['degraded_threshold' => 1.0, 'healthy_threshold' => 1.3],
    ];
    $updatedTargets = [
        1 => ['degraded_threshold' => 1.0, 'healthy_threshold' => 1.4],
        3 => ['degraded_threshold' => 1.0, 'healthy_threshold' => 1.3],
    ];

    $baseKey = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[1, 3], $range, $baseTargets]);
    $sameKey = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[3, 1], $range, $baseTargets]);
    $updatedKey = weather_cache_invoke_private($route, 'getDepartmentWeatherCacheKey', [[1, 3], $range, $updatedTargets]);

    expect($baseKey)->toBe($sameKey);
    expect($baseKey)->not->toBe($updatedKey);
});

it('falls back to resolver directly when cache ttl is disabled', function (): void {
    $route = new moduleWeatherAPIRoute();
    putenv('DEPARTMENTS_WEATHER_CACHE_TTL=0');

    $calls = 0;
    $result = weather_cache_invoke_private($route, 'withCachedDepartmentWeatherTimeline', [
        [1, 3, 5],
        [
            'start' => new DateTime('2026-03-24 00:00:00'),
            'endExclusive' => new DateTime('2026-03-25 00:00:00'),
        ],
        static function () use (&$calls): array {
            $calls++;

            return ['ok' => true];
        },
    ]);

    expect($result)->toBe(['ok' => true]);
    expect($calls)->toBe(1);
});
