<?php

use App\Services\Forecasting\Contracts\ForecastFeature;
use App\Services\Forecasting\FeatureSpec;
use App\Services\Forecasting\LeakDetector;
use App\Services\Forecasting\LeakReport;
use Carbon\Carbon;
use Carbon\CarbonInterface;

function makeFeature(string $name, array $offsetsInDays): ForecastFeature
{
    return new class($name, $offsetsInDays) implements ForecastFeature
    {
        /** @param array<int, int> $offsetsInDays */
        public function __construct(
            private readonly string $featureName,
            private readonly array $offsetsInDays,
        ) {}

        public function name(): string
        {
            return $this->featureName;
        }

        public function valueFor(CarbonInterface $targetMonday): float
        {
            return 0.0;
        }

        public function sourceDates(CarbonInterface $targetMonday): array
        {
            return array_map(
                fn (int $offset): CarbonInterface => $targetMonday->copy()->addDays($offset),
                $this->offsetsInDays,
            );
        }
    };
}

it('passes when every feature reads strictly before the target Monday', function () {
    $spec = new FeatureSpec(
        modelLabel: 'test',
        features: [
            makeFeature('lag_1w', [-7]),
            makeFeature('lag_4w', [-7, -14, -21, -28]),
        ],
    );

    $report = (new LeakDetector)->validate($spec, [Carbon::parse('2024-06-03')]);

    expect($report)->toBeInstanceOf(LeakReport::class)
        ->and($report->hasLeaks())->toBeFalse()
        ->and($report->leaks)->toBe([]);
});

it('flags a feature whose source date IS the target Monday', function () {
    $spec = new FeatureSpec(
        modelLabel: 'test',
        features: [makeFeature('same_day', [0])],
    );

    $report = (new LeakDetector)->validate($spec, [Carbon::parse('2024-06-03')]);

    expect($report->hasLeaks())->toBeTrue()
        ->and($report->leaks)->toHaveCount(1)
        ->and($report->leaks[0]['feature'])->toBe('same_day');
});

it('flags a feature whose source date is AFTER the target Monday', function () {
    $spec = new FeatureSpec(
        modelLabel: 'test',
        features: [makeFeature('future', [7])],
    );

    $report = (new LeakDetector)->validate($spec, [Carbon::parse('2024-06-03')]);

    expect($report->hasLeaks())->toBeTrue()
        ->and($report->leaks[0]['feature'])->toBe('future')
        ->and($report->leaks[0]['target_monday'])->toBe('2024-06-03')
        ->and($report->leaks[0]['source_date'])->toBe('2024-06-10');
});

it('checks every training week, not just the first', function () {
    $spec = new FeatureSpec(
        modelLabel: 'test',
        features: [makeFeature('lag_1w', [-7])],
    );

    $weeks = [
        Carbon::parse('2024-06-03'),
        Carbon::parse('2024-06-10'),
        Carbon::parse('2024-06-17'),
    ];

    $report = (new LeakDetector)->validate($spec, $weeks);

    expect($report->hasLeaks())->toBeFalse();
});

it('reports multiple leaks across multiple features', function () {
    $spec = new FeatureSpec(
        modelLabel: 'test',
        features: [
            makeFeature('clean', [-7]),
            makeFeature('leaky_one', [0]),
            makeFeature('leaky_two', [3]),
        ],
    );

    $report = (new LeakDetector)->validate($spec, [Carbon::parse('2024-06-03')]);

    expect($report->hasLeaks())->toBeTrue()
        ->and($report->leaks)->toHaveCount(2);

    $featureNames = array_column($report->leaks, 'feature');
    expect($featureNames)->toContain('leaky_one', 'leaky_two')
        ->and($featureNames)->not->toContain('clean');
});
