"""Independent reconstruction from raw responses; does not import analyze.py."""
import csv
import datetime
import gzip
import itertools
import json
import math
from collections import defaultdict
from fractions import Fraction as F
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]


def close(actual, expected):
    assert abs(float(actual) - float(expected)) <= 0.0000011, (actual, expected)


def season(month):
    return {12: "DJF", 1: "DJF", 2: "DJF", 3: "MAM", 4: "MAM", 5: "MAM", 6: "JJA", 7: "JJA", 8: "JJA", 9: "SON", 10: "SON", 11: "SON"}[month]


def main():
    observations = {}
    for file in sorted((ROOT / "data/raw").glob("*.json.gz")):
        for row in json.loads(gzip.decompress(file.read_bytes()))["data"]:
            key = datetime.datetime.fromisoformat(row["from"].replace("Z", "+00:00"))
            assert row["to"] == (key + datetime.timedelta(minutes=30)).isoformat(timespec="minutes").replace("+00:00", "Z")
            if key in observations:
                assert observations[key] == row
            observations[key] = row
    days = {}
    for year in (2023, 2024, 2025):
        date = datetime.date(year, 1, 1)
        while date.year == year:
            keys = [datetime.datetime.combine(date, datetime.time(), tzinfo=datetime.timezone.utc) + datetime.timedelta(minutes=30 * i) for i in range(48)]
            entries = [observations.get(key) for key in keys]
            vals = [entry["intensity"].get("actual") if entry else None for entry in entries]
            if all(type(v) in (int, float) and math.isfinite(v) and v >= 0 for v in vals):
                days[date.isoformat()] = [F(str(v)) for v in vals]
            date += datetime.timedelta(days=1)
    train = {d: v for d, v in days.items() if d.startswith("2023-")}
    groups = {"annual": list(train.values())}
    for s in ("DJF", "MAM", "JJA", "SON"):
        groups[s] = [v for d, v in train.items() if season(int(d[5:7])) == s]
    orders = {name: [t for _, t in sorted((sum(v[t] for v in vectors) / len(vectors), t) for t in range(48))] for name, vectors in groups.items()}
    primary = json.loads((ROOT / "results/R1.json").read_text())
    display = json.loads((ROOT / "results/R2.json").read_text())
    profiles = json.loads((ROOT / "results/profiles.json").read_text())
    for name, order in orders.items():
        assert order == profiles[name]["order"]
        assert len(order) == len(set(order)) == 48
    raw_rows = list(csv.DictReader(gzip.decompress((ROOT / "results/daily.csv.gz").read_bytes()).decode().splitlines()))
    aggregates = defaultdict(lambda: [F(0), F(0), F(0), 0, 0, 0, 0, 0])
    grouped = defaultdict(list)
    checked = 0
    for row in raw_rows:
        date, k, policy = row["date"], int(row["slots"]), row["policy"]
        values = days[date]
        prediction = orders["annual"] if policy == "annual" else orders[season(int(date[5:7]))] if policy == "seasonal" else [t for _, t in sorted((v, t) for t, v in enumerate(values))]
        chosen_slots = set(prediction[:k])
        assert len(chosen_slots) == k
        total = sum(values)
        chosen = sum(value for t, value in enumerate(values) if t in chosen_slots)
        early = sum(values[:k])
        uniform = F(k, 48) * total
        assert (total, chosen, early) == tuple(F(row[field]) for field in ("total_intensity_sum", "selected_intensity_sum", "immediate_intensity_sum"))
        oracle = sum(sorted(values)[:k])
        assert oracle <= chosen and oracle <= uniform and oracle <= early
        if k == 48:
            assert chosen == early == uniform
        bucket = aggregates[(date[:4], k, policy)]
        bucket[0] += total
        bucket[1] += chosen
        bucket[2] += early
        bucket[3] += 1
        bucket[4] += chosen < uniform
        bucket[5] += chosen == uniform
        bucket[6] += chosen > uniform
        bucket[7] += chosen > early
        if k in (12, 24, 36, 42, 48):
            grouped[(date[:4], k, policy)].append((date, total, chosen))
        checked += 1
    scenarios, monthly, quantiles, scaling = 0, 0, 0, 0
    for (year, k, policy), (total, chosen, early, count, lower, equal, higher, above_early) in aggregates.items():
        for percent in (0, 25, 50, 75):
            b = F(percent, 100)
            x = primary["years"][year][f"k{k}"][f"b{percent}"][policy]
            energy2 = 48 * b + k * (1 - b)
            scheduled = b * total + (1 - b) * chosen
            baseline = energy2 * total / 48
            first = b * total + (1 - b) * early
            reduction = 100 * (baseline - scheduled) / baseline
            expected = {
                "assigned_carbon_gco2": scheduled / 2, "uniform_carbon_gco2": baseline / 2,
                "immediate_carbon_gco2": first / 2, "daily_energy_kwh": energy2 / 2,
                "mean_intensity_gco2_per_kwh": scheduled / count / energy2,
                "reduction_uniform_percent": reduction,
                "reduction_immediate_percent": 100 * (first - scheduled) / first,
                "higher_days_percent": F(higher * 100, count),
                "utilization_percent": F(k * 100, 48),
            }
            for field, value in expected.items():
                close(x[field], value)
            if k in (12, 24, 36, 42, 48) and policy == "seasonal" and percent in (0, 50):
                label = {12: "u25", 24: "u50", 36: "u75", 42: "u87_5", 48: "u100"}[k]
                shown = display["y" + year][label]
                field = "seasonal_percent" if percent == 0 else "seasonal_half_idle_percent"
                close(shown[field], round(float(reduction), 2))
                assert shown["higher_days"] == higher and shown["days"] == count
            assert (x["days"], x["lower_days"], x["equal_days"], x["higher_days"], x["higher_than_immediate_days"]) == (count, lower, equal, higher, above_early)
            no_idle_reduction = 100 * (1 - chosen / (F(k, 48) * total))
            assert reduction == no_idle_reduction * ((1 - b) * k / energy2)
            scaling += 1
            if k in (12, 24, 36, 42, 48):
                daily, months = [], defaultdict(lambda: [F(0), F(0)])
                for date, dtotal, dchosen in grouped[(year, k, policy)]:
                    base = energy2 * dtotal / 48
                    cost = b * dtotal + (1 - b) * dchosen
                    daily.append(100 * (base - cost) / base)
                    months[str(int(date[5:7]))][0] += base
                    months[str(int(date[5:7]))][1] += cost
                for month, (base, cost) in months.items():
                    close(x["monthly_reduction_uniform_percent"][month], 100 * (base - cost) / base)
                    monthly += 1
                daily.sort()
                for q in (10, 50, 90):
                    close(x["daily_reduction_percent_quantiles"][str(q)], daily[math.ceil(count * q / 100) - 1])
                    quantiles += 1
            scenarios += 1
    exhaustive = 0
    for values in ([3, 3, 3, 3], [8, 2, 7, 1, 5, 0, 9, 3], [1, 1, 4, 4, 2, 2], list(range(8))):
        for k in range(1, len(values) + 1):
            predicted = sum(sorted(values)[:k])
            actual = min(sum(values[t] for t in selection) for selection in itertools.combinations(range(len(values)), k))
            assert predicted == actual
            exhaustive += 1
    constant_fixtures = 0
    for k in range(1, 49):
        for b in (F(0), F(1, 4), F(1, 2), F(3, 4), F(1)):
            power = sum(b + (1 - b) * int(t < k) for t in range(48))
            uniform = 48 * (b + (1 - b) * F(k, 48))
            assert power == uniform
            assert 7 * power == 7 * uniform
            constant_fixtures += 1
    assert checked == primary["daily_rows"]
    assert scenarios == primary["annual_scenarios"]
    out = {"raw_reconstructed_daily_rows": checked, "annual_scenarios_checked": scenarios,
           "monthly_values_checked": monthly, "daily_quantiles_checked": quantiles,
           "exact_idle_scaling_checks": scaling, "exhaustive_small_vector_optima": exhaustive,
           "work_energy_constant_signal_fixtures": constant_fixtures,
           "training_days": len(train), "evaluation_days": {str(y): sum(d.startswith(str(y)) for d in days) for y in (2024, 2025)},
           "status": "passed"}
    (ROOT / "results/validation.json").write_text(json.dumps(out, indent=2, sort_keys=True) + "\n")
    print(json.dumps(out))


if __name__ == "__main__":
    main()
