"""Utilities for parsing JAO auction responses."""

from __future__ import annotations

from typing import Any, List, Tuple

import pandas as pd


def extract_auction_ids(obj: Any) -> List[str]:
    """Extract auction identifiers from nested JSON.

    JAO uses either `auctionid` or `identification` depending on endpoint.
    """
    ids: List[str] = []
    if isinstance(obj, dict):
        if "auctionid" in obj:
            ids.append(obj["auctionid"])
        if "identification" in obj:
            ids.append(obj["identification"])
        for v in obj.values():
            ids.extend(extract_auction_ids(v))
    elif isinstance(obj, list):
        for v in obj:
            ids.extend(extract_auction_ids(v))
    return list(dict.fromkeys(ids))


def extract_corridor_codes(obj: Any) -> List[str]:
    """Extract corridor codes from nested JSON."""
    codes: List[str] = []
    if isinstance(obj, dict):
        for key in ("value", "corridor", "code", "corridorcode", "corridorCode"):
            val = obj.get(key)
            if isinstance(val, str):
                codes.append(val)
        for v in obj.values():
            codes.extend(extract_corridor_codes(v))
    elif isinstance(obj, list):
        for v in obj:
            codes.extend(extract_corridor_codes(v))
    return list(dict.fromkeys(codes))


def find_offered_capacity(obj: Any) -> float | None:
    """Try to detect offered capacity values inside the payload.

    Returns the maximum offered capacity found, or None if missing.
    """
    values: List[float] = []

    def walk(x: Any) -> None:
        if isinstance(x, dict):
            for k, v in x.items():
                lk = str(k).lower()
                if "offered" in lk and ("capacity" in lk or "atc" in lk):
                    try:
                        values.append(float(v))
                    except Exception:
                        pass
                walk(v)
        elif isinstance(x, list):
            for v in x:
                walk(v)

    walk(obj)
    return max(values) if values else None


def _pick_numeric_column(df: pd.DataFrame, candidates: List[str]) -> Tuple[str | None, pd.Series | None]:
    for col in df.columns:
        lc = col.lower()
        if any(c in lc for c in candidates):
            series = pd.to_numeric(df[col], errors="coerce")
            if series.notna().any():
                return col, series
    return None, None


def _find_bid_records(obj: Any) -> List[dict]:
    records: List[dict] = []

    def walk(x: Any) -> None:
        if isinstance(x, dict):
            keys = {k.lower() for k in x.keys()}
            has_price = any("price" in k for k in keys)
            has_qty = any(k in keys for k in ("quantity", "capacity", "volume", "mw", "qty", "awarded", "allocated", "accepted")) or any("mw" in k for k in keys)
            if has_price and has_qty:
                records.append(x)
            for v in x.values():
                walk(v)
        elif isinstance(x, list):
            for v in x:
                walk(v)

    walk(obj)
    return records


def prepare_bid_dataframe(bids_payload: Any):
    """Return a cleaned DataFrame with price and quantity columns.

    Returns
    -------
    df, price_col, y_col, raw_df
    """
    bid_records = _find_bid_records(bids_payload)
    if not bid_records:
        return None, None, None, None

    raw_df = pd.json_normalize(bid_records)

    price_col, _ = _pick_numeric_column(raw_df, ["price", "eur", "euro"])
    awarded_col, _ = _pick_numeric_column(raw_df, ["awarded", "allocated", "accepted", "cleared"])
    qty_col, _ = _pick_numeric_column(raw_df, ["quantity", "capacity", "volume", "mw", "qty"])

    if price_col is None or (awarded_col is None and qty_col is None):
        return raw_df, price_col, awarded_col or qty_col, raw_df

    y_col = awarded_col or qty_col
    df = raw_df[[price_col, y_col]].copy()
    df[price_col] = pd.to_numeric(df[price_col], errors="coerce")
    df[y_col] = pd.to_numeric(df[y_col], errors="coerce")
    df = df.dropna()
    return df, price_col, y_col, raw_df


def prepare_curtailment_dataframe(curtailments: Any):
    """Return a cleaned DataFrame for curtailments.

    Adds:
    - curtailedCapacity = originalCapacity - remainingCapacity (if available)
    - periodStart / periodDate
    - reason (falls back to "Unknown")
    """
    if not curtailments:
        return None

    df = pd.DataFrame(curtailments)
    if df.empty:
        return df

    if "isCancelled" in df.columns:
        df = df[~df["isCancelled"].fillna(False)]

    if "originalCapacity" in df.columns and "remainingCapacity" in df.columns:
        df["curtailedCapacity"] = df["originalCapacity"] - df["remainingCapacity"]

    if "curtailmentPeriodStart" in df.columns:
        df["periodStart"] = pd.to_datetime(df["curtailmentPeriodStart"], errors="coerce")
        df["periodDate"] = df["periodStart"].dt.date

    reason = None
    if "curtailmentReason" in df.columns:
        reason = df["curtailmentReason"]
    if "reasonDescription" in df.columns:
        reason = reason.fillna(df["reasonDescription"]) if reason is not None else df["reasonDescription"]

    if reason is None:
        df["reason"] = "Unknown"
    else:
        df["reason"] = reason.fillna("Unknown")

    return df
