#!/usr/bin/env python3
"""Join Deye inverter export (wide xlsx) with OTE 15min sell prices for BA81-style analysis.

OTE CSV: regenerate from EMS DB (MCP or psql), example:

  SELECT string_agg(
    to_char((interval_start AT TIME ZONE 'Europe/Prague')::date, 'YYYY-MM-DD') || ',' ||
    to_char(interval_start AT TIME ZONE 'Europe/Prague', 'HH24:MI') || ',' ||
    trim(to_char(sell_raw_price_czk_kwh, 'FM9999990.0000')),
    chr(10) ORDER BY interval_start
  )
  FROM ems.market_interval_price
  WHERE market_source = 'OTE_CZ'
    AND (interval_start AT TIME ZONE 'Europe/Prague')::date IN (...);

Convention in sample logs: negative Battery Power(W) ≈ charging, positive ≈ discharging.
Total Grid Power(W): small positive ≈ little/no export (sign per site firmware).

Requires: openpyxl. Use read_only=False (these exports report max_row=1 in read_only mode).
"""
from __future__ import annotations

import argparse
import statistics as st
from collections import defaultdict
from datetime import datetime
from pathlib import Path

import openpyxl

COLS = [
    "Time",
    "Total Solar Power(W)",
    "Total Inverter Output Power(W)",
    "Total Grid Power(W)",
    "Battery Power(W)",
    "SoC(%)",
]


def load_ote_csv(path: Path) -> dict[tuple[str, str], float]:
    ote: dict[tuple[str, str], float] = {}
    for line in path.read_text().splitlines():
        line = line.strip()
        if not line:
            continue
        d, hm, s = line.split(",")
        ote[(d, hm)] = float(s)
    return ote


def floor_15(dt: datetime) -> datetime:
    m = (dt.minute // 15) * 15
    return dt.replace(minute=m, second=0, microsecond=0)


def slot_key(dt: datetime) -> tuple[str, str]:
    f = floor_15(dt)
    return f.strftime("%Y-%m-%d"), f.strftime("%H:%M")


def load_inverter_rows(fp: Path) -> list[dict[str, object]]:
    wb = openpyxl.load_workbook(fp, read_only=False, data_only=True)
    ws = wb.active
    it = ws.iter_rows(values_only=True)
    header = next(it)
    idx = {str(h).strip(): i for i, h in enumerate(header) if h}
    rows: list[dict[str, object]] = []
    for r in it:
        if not r or r[idx["Time"]] is None:
            continue
        rows.append({c: r[idx[c]] for c in COLS})
    wb.close()
    return rows


def main() -> None:
    p = argparse.ArgumentParser(description=__doc__)
    p.add_argument("--ote-csv", type=Path, required=True)
    p.add_argument("xlsx", type=Path, nargs="+")
    args = p.parse_args()
    ote = load_ote_csv(args.ote_csv)

    for fp in args.xlsx:
        data = load_inverter_rows(fp)
        neg: list[tuple[float, datetime, dict]] = []
        for r in data:
            t = r["Time"]
            if isinstance(t, str):
                t = datetime.strptime(t, "%Y/%m/%d %H:%M:%S")
            dk, hm = slot_key(t)
            sell = ote.get((dk, hm))
            if sell is None or sell >= 0:
                continue
            neg.append((sell, t, r))

        print(f"\n=== {fp.name}  rows={len(data)}  OTE sell<0 samples={len(neg)}")
        if not neg:
            continue
        socs = [float(x[2]["SoC(%)"]) for x in neg]
        grids = [float(x[2]["Total Grid Power(W)"]) for x in neg]
        bats = [float(x[2]["Battery Power(W)"]) for x in neg]
        sols = [float(x[2]["Total Solar Power(W)"]) for x in neg]
        print(f"  SoC %:   mean={st.mean(socs):.1f}  min={min(socs):.0f}  max={max(socs):.0f}")
        print(f"  Grid W:  mean={st.mean(grids):.0f}  med={st.median(grids):.0f}")
        print(f"  Bat W:   mean={st.mean(bats):.0f}  med={st.median(bats):.0f}")
        print(f"  Solar W: mean={st.mean(sols):.0f}  med={st.median(sols):.0f}")

        buckets: dict[str, list] = defaultdict(list)
        for sell, t, r in neg:
            if t.hour < 9 or t.hour > 18:
                continue
            _, hm = slot_key(t)
            buckets[hm].append((sell, r))
        print("  15min buckets (OTE<0, 09-18h) medians:")
        for hm in sorted(buckets.keys()):
            b = buckets[hm]
            sell = b[0][0]
            socs_b = [float(x[1]["SoC(%)"]) for x in b]
            print(
                f"    {hm}  sell={sell:+.3f}  n={len(b):2d}  "
                f"SoC_med={st.median(socs_b):.0f}%  "
                f"Pgrid_med={st.median([float(x[1]['Total Grid Power(W)']) for x in b]):.0f}W  "
                f"Psol_med={st.median([float(x[1]['Total Solar Power(W)']) for x in b]):.0f}W"
            )


if __name__ == "__main__":
    main()
