"""
evaluate_indicator — runs a pandas-ta (or custom) indicator against real OHLC data.

Called directly from the MCP server's async handle_tool_call, so it can await
the DataAPI without subprocess overhead.

Returns a JSON object with a `values` array of {timestamp, ...} records, where
timestamp is a Unix second integer and value fields hold floats (or null for NaN).
"""

import json
import logging
from pathlib import Path
from typing import Any

import pandas as pd

log = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# Input routing — which series each pandas-ta function expects
# ---------------------------------------------------------------------------

# Maps pandas_ta_name → tuple of column names from the OHLCV dataframe
# Columns available: open, high, low, close, volume
# "volume" is fetched via extra_columns=["volume"]

_INPUTS: dict[str, tuple[str, ...]] = {
    # Close only
    "sma": ("close",),
    "ema": ("close",),
    "wma": ("close",),
    "dema": ("close",),
    "tema": ("close",),
    "trima": ("close",),
    "kama": ("close",),
    "t3": ("close",),
    "hma": ("close",),
    "alma": ("close",),
    "midpoint": ("close",),
    "rsi": ("close",),
    "macd": ("close",),
    "mom": ("close",),
    "roc": ("close",),
    "trix": ("close",),
    "cmo": ("close",),
    "ao": ("high", "low"),   # ao uses midprice (high, low)
    "apo": ("close",),
    "coppock": ("close",),
    "dpo": ("close",),
    "fisher": ("high", "low"),
    "rvgi": ("open", "high", "low", "close"),
    "kst": ("close",),
    "stdev": ("close",),
    "linreg": ("close",),
    "slope": ("close",),
    "vwma": ("close", "volume"),
    "obv": ("close", "volume"),
    "pvt": ("close", "volume"),
    "efi": ("close", "volume"),
    # High + Low
    "hl2": ("high", "low"),
    "midprice": ("high", "low"),
    # High + Low + Close
    "hlc3": ("high", "low", "close"),
    "atr": ("high", "low", "close"),
    "kc": ("high", "low", "close"),
    "donchian": ("high", "low", "close"),
    "stoch": ("high", "low", "close"),
    "stochrsi": ("high", "low", "close"),
    "cci": ("high", "low", "close"),
    "willr": ("high", "low", "close"),
    "adx": ("high", "low", "close"),
    "aroon": ("high", "low", "close"),
    "uo": ("high", "low", "close"),
    "psar": ("high", "low", "close"),
    "vortex": ("high", "low", "close"),
    "chop": ("high", "low", "close"),
    "supertrend": ("high", "low", "close"),
    "ichimoku": ("high", "low", "close"),
    # Open + High + Low + Close
    "ohlc4": ("open", "high", "low", "close"),
    "bop": ("open", "high", "low", "close"),
    # High + Low + Close + Volume
    "mfi": ("high", "low", "close", "volume"),
    "ad": ("high", "low", "close", "volume"),
    "adosc": ("high", "low", "close", "volume"),
    "cmf": ("high", "low", "close", "volume"),
    "eom": ("high", "low", "close", "volume"),
    "kvo": ("high", "low", "close", "volume"),
    # VWAP needs datetime index — handled specially
    "vwap": ("high", "low", "close", "volume"),
}

_NEEDS_VOLUME = {name for name, cols in _INPUTS.items() if "volume" in cols}


async def evaluate_indicator(
    symbol: str,
    from_time: Any,
    to_time: Any,
    period_seconds: int,
    pandas_ta_name: str,
    parameters: dict,
) -> list:
    """
    Fetch OHLC data and evaluate a pandas-ta indicator.

    Returns a list containing a single MCP TextContent with JSON:
    {
        "symbol": ...,
        "period_seconds": ...,
        "pandas_ta_name": ...,
        "parameters": {...},
        "candle_count": N,
        "columns": ["timestamp", "value"] or ["timestamp", "col1", "col2", ...],
        "values": [{"timestamp": <unix_s>, "value": <float|null>}, ...]
    }
    """
    from mcp.types import TextContent

    try:
        import pandas_ta as ta
    except ImportError:
        return [TextContent(type="text", text=json.dumps({"error": "pandas_ta not installed"}))]

    name_lower = pandas_ta_name.lower()

    # For custom indicators, register them with pandas-ta first, then resolve
    # input columns from their stored metadata.
    if name_lower.startswith("custom_"):
        import os
        from dexorder.tools.python_tools import setup_custom_indicators, get_category_manager
        setup_custom_indicators(Path(os.environ.get("DATA_DIR", "data")))

        fn = getattr(ta, name_lower, None)
        if fn is None:
            return [TextContent(type="text", text=json.dumps({
                "error": (
                    f"Custom indicator '{pandas_ta_name}' not found after registering "
                    "custom indicators. Make sure the indicator was created with "
                    "PythonWrite(category='indicator', name='...') and that its "
                    "implementation.py defines a function matching the sanitized name."
                )
            }))]

        # Get input_series from the indicator's metadata.
        # Look up by the stored pandas_ta_name field (written at creation time),
        # which is the reliable reverse mapping from ta_name → directory.
        # Fall back to display-name matching for indicators created before this
        # field was added.
        mgr = get_category_manager()
        all_items = mgr.list_items("indicator")
        read_result = {"exists": False}
        for item in all_items.get("items", []):
            meta = item.get("metadata", {})
            # Primary: match stored pandas_ta_name field (exact, set at write time)
            if meta.get("pandas_ta_name") == name_lower:
                read_result = mgr.read("indicator", item.get("name", ""))
                break
            # Fallback: infer from display name (legacy indicators without the field)
            item_name = item.get("name", "")
            if "custom_" + item_name.lower().replace("-", "_").replace(" ", "_") == name_lower:
                read_result = mgr.read("indicator", item_name)
                break
        if read_result.get("exists") and read_result.get("metadata"):
            raw_series = read_result["metadata"].get("input_series") or ["close"]
            input_cols = tuple(raw_series)
        else:
            input_cols = ("close",)
    else:
        # Look up the pandas-ta function for built-in indicators
        fn = getattr(ta, name_lower, None)
        if fn is None:
            return [TextContent(type="text", text=json.dumps({
                "error": f"Unknown indicator '{pandas_ta_name}'. Check pandas_ta_name against the supported list."
            }))]

        # Determine required columns
        input_cols = _INPUTS.get(name_lower, ("close",))
    needs_volume = "volume" in input_cols

    # Fetch OHLC
    try:
        from dexorder.api import get_api
        api = get_api()
        df = await api.data.historical_ohlc(
            ticker=symbol,
            period_seconds=period_seconds,
            start_time=from_time,
            end_time=to_time,
            extra_columns=["volume"] if needs_volume else [],
        )
    except Exception as exc:
        log.exception("evaluate_indicator: OHLC fetch failed")
        return [TextContent(type="text", text=json.dumps({"error": f"OHLC fetch failed: {exc}"}))]

    if df.empty:
        return [TextContent(type="text", text=json.dumps({
            "error": f"No OHLC data for {symbol} in the requested range"
        }))]

    # VWAP already requires a DatetimeIndex — the OHLC df index is already a
    # DatetimeIndex, so no extra work needed here.

    # Build positional args
    args = []
    for col in input_cols:
        if col not in df.columns:
            return [TextContent(type="text", text=json.dumps({
                "error": f"Column '{col}' not in fetched dataframe (columns: {list(df.columns)})"
            }))]
        args.append(df[col])

    # Compute
    try:
        result = fn(*args, **parameters)
    except Exception as exc:
        log.exception("evaluate_indicator: computation failed")
        return [TextContent(type="text", text=json.dumps({
            "error": f"Indicator computation failed: {exc}"
        }))]

    # Convert DatetimeIndex → Unix seconds
    timestamps = (df.index.astype("int64") // 1_000_000_000).tolist()

    # Serialize output
    if isinstance(result, pd.DataFrame):
        columns = ["timestamp"] + list(result.columns)
        values = []
        for i, ts in enumerate(timestamps):
            row: dict[str, Any] = {"timestamp": int(ts)}
            for col in result.columns:
                v = result.iloc[i][col]
                row[col] = None if (isinstance(v, float) and pd.isna(v)) else float(v)
            values.append(row)
    elif isinstance(result, pd.Series):
        columns = ["timestamp", "value"]
        values = [
            {"timestamp": int(ts), "value": None if pd.isna(v) else float(v)}
            for ts, v in zip(timestamps, result.tolist())
        ]
    else:
        return [TextContent(type="text", text=json.dumps({
            "error": f"Unexpected indicator output type: {type(result).__name__}"
        }))]

    payload = {
        "symbol": symbol,
        "period_seconds": period_seconds,
        "pandas_ta_name": pandas_ta_name,
        "parameters": parameters,
        "candle_count": len(df),
        "columns": columns,
        "values": values,
    }

    return [TextContent(type="text", text=json.dumps(payload))]
