"""Frozen public-data accounting benchmark; Python standard library only."""
import csv
import datetime as dt
import gzip
import hashlib
import io
import json
import math
from collections import defaultdict
from fractions import Fraction
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
HIGHLIGHTS = (12, 24, 36, 42, 48)
POLICIES = ("annual", "seasonal", "hindsight")
IDLES = (0, 25, 50, 75)


def write_json(name, obj):
    dest = ROOT / "results" / name
    dest.parent.mkdir(parents=True, exist_ok=True)
    dest.write_text(json.dumps(obj, indent=2, sort_keys=True, allow_nan=False) + "\n")


def seasonal(month):
    return "DJF" if month in (12, 1, 2) else "MAM" if month <= 5 else "JJA" if month <= 8 else "SON"


def numeric(value):
    if isinstance(value, bool) or not isinstance(value, (int, float)) or not math.isfinite(value) or value < 0:
        return None
    return Fraction(str(value))


def source_days():
    manifest = json.loads((ROOT / "data/SOURCES.json").read_text())
    unique, duplicates = {}, 0
    for source in manifest:
        compressed = (ROOT / source["path"]).read_bytes()
        assert len(compressed) == source["compressed_bytes"]
        assert "sha256:" + hashlib.sha256(compressed).hexdigest() == source["compressed_sha256"]
        raw = gzip.decompress(compressed)
        assert len(raw) == source["bytes"]
        assert "sha256:" + hashlib.sha256(raw).hexdigest() == source["sha256"]
        records = json.loads(raw)["data"]
        assert len(records) == source["records"]
        for row in records:
            start, end = (dt.datetime.fromisoformat(row[x].replace("Z", "+00:00")) for x in ("from", "to"))
            assert start.utcoffset() == end.utcoffset() == dt.timedelta(0)
            assert start.minute in (0, 30) and start.second == start.microsecond == 0
            assert end - start == dt.timedelta(minutes=30)
            if start in unique:
                assert unique[start] == row
                duplicates += 1
            else:
                unique[start] = row
    observed, invalid = defaultdict(dict), []
    for timestamp, row in sorted(unique.items()):
        if timestamp.year not in (2023, 2024, 2025):
            continue
        value = numeric(row["intensity"].get("actual"))
        if value is None:
            invalid.append(row["from"])
        else:
            observed[timestamp.date()][timestamp.hour * 2 + timestamp.minute // 30] = value
    complete = {day: [values[t] for t in range(48)] for day, values in observed.items() if len(values) == 48}
    excluded = []
    date = dt.date(2023, 1, 1)
    while date.year <= 2025:
        if date not in complete:
            excluded.append({"date": date.isoformat(), "missing_slots": sorted(set(range(48)) - observed[date].keys())})
        date += dt.timedelta(days=1)
    for year in (2023, 2024, 2025):
        assert sum(d.year == year for d in complete) / (366 if year == 2024 else 365) >= .95
    audit = {"source_files": len(manifest), "duplicate_intervals": duplicates, "invalid_intervals": invalid, "excluded_dates": excluded}
    return complete, audit


def rank(values):
    return sorted(range(len(values)), key=lambda t: (values[t], t))


def rounded(value):
    return round(float(value), 6)


def percentile(values, percent):
    return sorted(values)[max(0, math.ceil(len(values) * percent / 100) - 1)]


def summarize(rows, k, idle):
    b = Fraction(idle, 100)
    totals = sum(row["total"] for row in rows)
    chosen = sum(row["selected"] for row in rows)
    early = sum(row["early"] for row in rows)
    flat = Fraction(k, 48) * totals
    selected_energy_carbon_twice = b * totals + (1 - b) * chosen
    flat_carbon_twice = b * totals + (1 - b) * flat
    early_carbon_twice = b * totals + (1 - b) * early
    power_slots = b * 48 + (1 - b) * k
    diffs = [Fraction(k, 48) * row["total"] - row["selected"] for row in rows]
    early_diffs = [row["early"] - row["selected"] for row in rows]
    out = {
        "days": len(rows), "utilization_percent": rounded(Fraction(100 * k, 48)), "idle_percent": idle,
        "daily_energy_kwh": rounded(power_slots / 2),
        "assigned_carbon_gco2": rounded(selected_energy_carbon_twice / 2),
        "uniform_carbon_gco2": rounded(flat_carbon_twice / 2),
        "immediate_carbon_gco2": rounded(early_carbon_twice / 2),
        "mean_intensity_gco2_per_kwh": rounded(selected_energy_carbon_twice / (len(rows) * power_slots)),
        "reduction_uniform_percent": rounded(100 * (flat_carbon_twice - selected_energy_carbon_twice) / flat_carbon_twice),
        "reduction_immediate_percent": rounded(100 * (early_carbon_twice - selected_energy_carbon_twice) / early_carbon_twice),
        "lower_days": sum(d > 0 for d in diffs), "equal_days": sum(d == 0 for d in diffs), "higher_days": sum(d < 0 for d in diffs),
        "higher_days_percent": rounded(Fraction(100 * sum(d < 0 for d in diffs), len(rows))),
        "higher_than_immediate_days": sum(d < 0 for d in early_diffs),
    }
    if k in HIGHLIGHTS:
        daily = [100 * (1 - (b * row["total"] + (1 - b) * row["selected"]) /
                        (b * row["total"] + (1 - b) * Fraction(k, 48) * row["total"])) for row in rows]
        out["daily_reduction_percent_quantiles"] = {str(q): rounded(percentile(daily, q)) for q in (10, 50, 90)}
        months = defaultdict(list)
        for row in rows:
            months[row["date"].month].append(row)
        month_reductions = {}
        for month, members in sorted(months.items()):
            total = sum(row["total"] for row in members)
            scheduled = b * total + (1 - b) * sum(row["selected"] for row in members)
            baseline = (b + (1 - b) * Fraction(k, 48)) * total
            month_reductions[str(month)] = rounded(100 * (1 - scheduled / baseline))
        out["monthly_reduction_uniform_percent"] = month_reductions
        out["monthly_min_percent"] = min(month_reductions.values())
        out["monthly_max_percent"] = max(month_reductions.values())
    return out


def main():
    days, audit = source_days()
    training = {d: x for d, x in days.items() if d.year == 2023}
    groups = {"annual": list(training.values())}
    for season in ("DJF", "MAM", "JJA", "SON"):
        groups[season] = [v for d, v in training.items() if seasonal(d.month) == season]
    profiles = {}
    for name, vectors in groups.items():
        sums = [sum(v[t] for v in vectors) for t in range(48)]
        profiles[name] = {"days": len(vectors), "means": [rounded(x / len(vectors)) for x in sums], "order": rank(sums)}
    buckets = defaultdict(list)
    daily_buffer = io.StringIO()
    writer = csv.writer(daily_buffer, lineterminator="\n")
    writer.writerow(["date", "slots", "policy", "total_intensity_sum", "selected_intensity_sum", "immediate_intensity_sum"])
    row_count = 0
    for day, values in sorted(days.items()):
        if day.year == 2023:
            continue
        total = sum(values)
        assert total > 0
        orders = {"annual": profiles["annual"]["order"], "seasonal": profiles[seasonal(day.month)]["order"], "hindsight": rank(values)}
        for policy, order in orders.items():
            selected, early = Fraction(0), Fraction(0)
            for k, t in enumerate(order, 1):
                selected += values[t]
                early += values[k - 1]
                row = {"date": day, "total": total, "selected": selected, "early": early}
                buckets[(day.year, k, policy)].append(row)
                writer.writerow([day.isoformat(), k, policy, str(total), str(selected), str(early)])
                row_count += 1
    years = {}
    for year in (2024, 2025):
        scenarios = {}
        for k in range(1, 49):
            scenarios[f"k{k}"] = {f"b{b}": {policy: summarize(buckets[(year, k, policy)], k, b) for policy in POLICIES} for b in IDLES}
        years[str(year)] = scenarios
    result = {"years": years, "training_days": len(training), "evaluation_days": {str(y): sum(d.year == y for d in days) for y in (2024, 2025)},
              "highlight_slots": list(HIGHLIGHTS), "slot_minutes": 30, "daily_rows": row_count, "annual_scenarios": 2 * 48 * len(IDLES) * len(POLICIES),
              "source_files": audit["source_files"], "idle_fractions_percent": list(IDLES)}
    write_json("R1.json", result)
    display = {"training_days": len(training), "evaluation_days": result["evaluation_days"], "idle_example_percent": 50}
    for year in ("2024", "2025"):
        display["y" + year] = {}
        for k, label in ((12, "u25"), (24, "u50"), (36, "u75"), (42, "u87_5"), (48, "u100")):
            row = years[year][f"k{k}"]["b0"]["seasonal"]
            display["y" + year][label] = {
                "utilization_percent": row["utilization_percent"],
                "seasonal_percent": round(row["reduction_uniform_percent"], 2),
                "seasonal_half_idle_percent": round(years[year][f"k{k}"]["b50"]["seasonal"]["reduction_uniform_percent"], 2),
                "annual_percent": round(years[year][f"k{k}"]["b0"]["annual"]["reduction_uniform_percent"], 2),
                "hindsight_percent": round(years[year][f"k{k}"]["b0"]["hindsight"]["reduction_uniform_percent"], 2),
                "against_immediate_percent": round(row["reduction_immediate_percent"], 2),
                "higher_days": row["higher_days"], "days": row["days"],
                "higher_days_percent": round(row["higher_days_percent"], 2),
                "monthly_min_percent": round(row["monthly_min_percent"], 2),
                "monthly_max_percent": round(row["monthly_max_percent"], 2),
                "daily_p10_percent": round(row["daily_reduction_percent_quantiles"]["10"], 2),
                "daily_p90_percent": round(row["daily_reduction_percent_quantiles"]["90"], 2),
            }
    write_json("R2.json", display)
    write_json("profiles.json", profiles)
    write_json("source-audit.json", audit)
    (ROOT / "results/daily.csv.gz").write_bytes(gzip.compress(daily_buffer.getvalue().encode(), mtime=0))
    for year in ("2024", "2025"):
        print(year, {k: {"no_idle": years[year][f"k{k}"]["b0"]["seasonal"]["reduction_uniform_percent"],
                         "half_idle": years[year][f"k{k}"]["b50"]["seasonal"]["reduction_uniform_percent"],
                         "higher_days": years[year][f"k{k}"]["b0"]["seasonal"]["higher_days"]} for k in HIGHLIGHTS})


if __name__ == "__main__":
    main()
