"""Hourly energy CSV validation and aggregation. Python standard library only.

python prepare_energy.py energy-synthetic-hourly.csv --output summary.json
No network requests, model training or credentials.
"""
import argparse
import csv
import json
import math
import re
from collections import defaultdict
from datetime import datetime
from pathlib import Path


def prepare(path):
    path = Path(path)
    if path.stat().st_size > 2_000_000:
        raise ValueError('CSV exceeds 2 MB')
    records, issues, seen = [], [], set()
    with path.open(encoding='utf-8-sig', newline='') as stream:
        reader = csv.DictReader(stream)
        required = ['timestamp', 'region', 'source', 'energy_kwh']
        if not reader.fieldnames or not set(required) <= set(reader.fieldnames) or len(set(reader.fieldnames)) != len(reader.fieldnames):
            raise ValueError('Required columns: ' + ', '.join(required))
        for line, row in enumerate(reader, 2):
            if line > 20001:
                raise ValueError('Maximum 20,000 data rows')
            try:
                if None in row or any(row.get(k) is None for k in reader.fieldnames):
                    raise ValueError('Column count mismatch')
                timestamp, region, source, raw = [row[k].strip() for k in required]
                if not re.fullmatch(r'\d{4}-\d{2}-\d{2}T\d{2}:00:00\+09:00', timestamp):
                    raise ValueError('Timestamp must be an hourly KST interval start')
                datetime.fromisoformat(timestamp)
                if not re.fullmatch(r'(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?', raw):
                    raise ValueError('Invalid energy_kwh')
                value = float(raw)
                if not math.isfinite(value) or not 0 <= value <= 1e12:
                    raise ValueError('Invalid energy_kwh')
                if source not in ('solar', 'wind', 'tidal') or not region or len(region) > 40:
                    raise ValueError('Invalid region or source')
                key = (timestamp, region, source)
                if key in seen:
                    raise ValueError('Duplicate timestamp, region and source')
                seen.add(key)
                records.append(dict(timestamp=timestamp, region=region, source=source, energy_kwh=value, line=line))
            except (ValueError, TypeError) as error:
                issues.append(dict(line=line, reason=str(error)))
    if not records:
        raise ValueError('No usable records')
    records.sort(key=lambda r: (r['timestamp'], r['region'], r['source']))
    totals, times = defaultdict(float), defaultdict(list)
    for row in records:
        group = (row['region'], row['source'])
        totals[group] += row['energy_kwh']
        times[group].append(datetime.fromisoformat(row['timestamp']))
    groups = [dict(region=k[0], source=k[1], total_kwh=v,
                   missing_hours=int((max(times[k]) - min(times[k])).total_seconds() / 3600) + 1 - len(times[k]))
              for k, v in totals.items()]
    return dict(version=1, source_file=path.name, unit='kWh', interval='1 hour', timezone='Asia/Seoul',
                usable=len(records), excluded=len(issues), issues=issues, groups=groups, records=records)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument('csv')
    parser.add_argument('--output', default='energy-summary.json')
    args = parser.parse_args()
    result = prepare(args.csv)
    Path(args.output).write_text(json.dumps(result, ensure_ascii=False, indent=2), encoding='utf-8')
    print(f"{result['usable']} usable, {result['excluded']} excluded -> {args.output}")
