#!/usr/bin/env python
"""Normalize provider exports into the leakage-safe PIT earnings calendar schema."""
from __future__ import annotations

import argparse
import datetime as dt
from pathlib import Path
from typing import Any

import pyarrow as pa
import pyarrow.csv as pacsv
import pyarrow.parquet as pq

from libs.labeler.reaction_date import compute_reaction_date


def _read_table(path: Path) -> pa.Table:
    suffix = path.suffix.lower()
    if suffix == ".parquet":
        return pq.read_table(str(path))
    if suffix in {".csv", ".txt"}:
        return pacsv.read_csv(str(path))
    raise ValueError(f"Unsupported input format: {path}")


def _coerce_date(value: Any) -> str | None:
    if value is None:
        return None
    if isinstance(value, dt.datetime):
        return value.date().isoformat()
    if isinstance(value, dt.date):
        return value.isoformat()
    text = str(value).strip()
    if not text:
        return None
    try:
        return dt.date.fromisoformat(text[:10]).isoformat()
    except ValueError:
        return None


def _coerce_bool(value: Any) -> bool:
    if isinstance(value, bool):
        return value
    if value is None:
        return False
    text = str(value).strip().lower()
    return text in {"1", "true", "t", "yes", "y"}


def _pick(row: dict[str, Any], *names: str) -> Any:
    for name in names:
        if name in row and row[name] is not None:
            return row[name]
    return None


def build_rows(table: pa.Table) -> list[dict[str, Any]]:
    rows: list[dict[str, Any]] = []
    for row in table.to_pylist():
        symbol = str(_pick(row, "symbol", "ticker", "trade_symbol") or "").strip().upper()
        as_of_date = _coerce_date(_pick(row, "as_of_date", "known_as_of_date", "published_date", "snapshot_date"))
        expected_event_date = _coerce_date(_pick(row, "expected_event_date", "event_date", "earnings_date", "next_earnings_date"))
        filing_time_bucket = str(_pick(row, "expected_filing_time_bucket", "filing_time_bucket", "timing_class") or "post_market")
        expected_reaction_date = _coerce_date(_pick(row, "expected_reaction_date", "reaction_date"))
        if expected_reaction_date is None and expected_event_date is not None:
            expected_reaction_date = compute_reaction_date(
                dt.date.fromisoformat(expected_event_date),
                filing_time_bucket,
            ).isoformat()
        if not symbol or as_of_date is None or expected_reaction_date is None:
            continue
        rows.append(
            {
                "symbol": symbol,
                "as_of_date": as_of_date,
                "expected_reaction_date": expected_reaction_date,
                "expected_event_date": expected_event_date,
                "filing_time_bucket": filing_time_bucket,
                "confidence": _pick(row, "confidence", "resolver_confidence"),
                "revision_count": _pick(row, "revision_count"),
                "is_cancelled": _coerce_bool(_pick(row, "is_cancelled", "cancelled")),
                "source": _pick(row, "source", "provider"),
            }
        )
    rows.sort(key=lambda r: (r["symbol"], r["as_of_date"], r["expected_reaction_date"]))
    return rows


def main() -> None:
    parser = argparse.ArgumentParser(description=__doc__)
    parser.add_argument("--input", required=True, help="Provider export CSV/Parquet")
    parser.add_argument(
        "--output",
        default="data/reference/earnings_calendar_pit.parquet",
        help="Normalized PIT calendar parquet path",
    )
    args = parser.parse_args()

    input_path = Path(args.input)
    output_path = Path(args.output)
    output_path.parent.mkdir(parents=True, exist_ok=True)

    table = _read_table(input_path)
    rows = build_rows(table)
    pq.write_table(pa.Table.from_pylist(rows), output_path)
    print(f"wrote {len(rows)} rows to {output_path}")


if __name__ == "__main__":
    main()
