# src/web/api/v2/routers/calibration_strategies.py

import logging
import re
import uuid
from pathlib import Path
from typing import Dict

from fastapi import APIRouter, Depends, HTTPException, Request
from fastapi.responses import JSONResponse, HTMLResponse

from src.data.storage import StorageManager
from src.strategies.registry import STRATEGY_REGISTRY
from src.calibration.strategies_inspector import (
    inspect_strategies_config,
    list_available_strategies,
)
from src.calibration.reports.strategies_report import generate_strategies_report_pdf

from ..schemas.calibration_strategies import (
    CalibrationStrategiesInspectRequest,
    CalibrationStrategiesInspectResponse,
    CalibrationStrategiesValidateResponse,
)

logger = logging.getLogger("tradingbot.api.v2")

router = APIRouter(
    prefix="/calibration/strategies",
    tags=["calibration"],
)

WF_JOBS: Dict[str, Dict] = {}

def get_storage() -> StorageManager:
    return StorageManager.from_env()


@router.get("/catalog")
def strategy_catalog():

    strategies = list_available_strategies()

    enriched = []

    for s in strategies:

        strategy_id = s["strategy_id"]
        strategy_class = STRATEGY_REGISTRY[strategy_id]

        schema = strategy_class.parameters_schema()

        parameters_meta = []

        for name, meta in schema.items():

            parameters_meta.append({
                "name": name,
                "type": meta.get("type"),
                "default_value": meta.get("default"),
                "choices": meta.get("choices"),
                "min": meta.get("min"),
                "max": meta.get("max"),
            })

        enriched.append({
            "strategy_id": strategy_id,
            "name": s["name"],
            "params": list(schema.keys()),
            "parameters_meta": parameters_meta,
        })

    return {"strategies": enriched}


@router.post("/inspect", response_model=CalibrationStrategiesInspectResponse)
def inspect_strategies(
    payload: CalibrationStrategiesInspectRequest,
    storage: StorageManager = Depends(get_storage),
):
    df = storage.load_ohlcv(symbol=payload.symbol, timeframe=payload.timeframe)
    if df is None or df.empty:
        raise HTTPException(status_code=400, detail="No OHLCV data found. Run Step 1 first.")

    from .calibration_data import analyze_data_quality
    data_quality = analyze_data_quality(df, payload.timeframe)

    result = inspect_strategies_config(
        storage=storage,
        payload=payload,
        data_quality=data_quality,
        include_series=False,
        df=df,
    )
    return CalibrationStrategiesInspectResponse(**result)

@router.post("/validate", response_model=CalibrationStrategiesValidateResponse)
def validate_strategies(
    payload: CalibrationStrategiesInspectRequest,
    storage: StorageManager = Depends(get_storage),
):
    df = storage.load_ohlcv(symbol=payload.symbol, timeframe=payload.timeframe)
    if df is None or df.empty:
        raise HTTPException(status_code=400, detail="No OHLCV data found. Run Step 1 first.")

    from .calibration_data import analyze_data_quality
    data_quality = analyze_data_quality(df, payload.timeframe)

    result = inspect_strategies_config(
        storage=storage,
        payload=payload,
        data_quality=data_quality,
        include_series=True,
        df=df,
    )
    return CalibrationStrategiesValidateResponse(**result)

@router.post("/report")
def report_strategies(
    payload: CalibrationStrategiesInspectRequest,
    storage: StorageManager = Depends(get_storage),
):
    logger.info(f"🧾 Generating strategies report | {payload.symbol} {payload.timeframe}")

    df = storage.load_ohlcv(symbol=payload.symbol, timeframe=payload.timeframe)
    if df is None or df.empty:
        raise HTTPException(status_code=400, detail="No OHLCV data found. Run Step 1 first.")

    from .calibration_data import analyze_data_quality
    data_quality = analyze_data_quality(df, payload.timeframe)

    result = inspect_strategies_config(
        storage=storage,
        payload=payload,
        data_quality=data_quality,
        include_series=True,
        df=df,
    )

    # ---------------------------------------------
    # Prepare PDF output path (outside src)
    # ---------------------------------------------
    project_root = Path(__file__).resolve().parents[4]  # .../src
    # project_root currently points to src/web/api/v2/routers -> parents[4] == src
    project_root = project_root.parent  # repo root

    reports_dir = project_root / "reports" / "strategies"
    reports_dir.mkdir(parents=True, exist_ok=True)

    safe_symbol = re.sub(r"[^a-zA-Z0-9_-]", "_", payload.symbol)
    filename = f"strategies_report_{safe_symbol}_{payload.timeframe}_{uuid.uuid4().hex}.pdf"

    symbol_dir = reports_dir / safe_symbol
    symbol_dir.mkdir(exist_ok=True)

    output_path = symbol_dir / filename

    generate_strategies_report_pdf(
        output_path=output_path,
        context={
            "Symbol": payload.symbol,
            "Timeframe": payload.timeframe,
            "Account equity": payload.account_equity,
        },
        config={
            "Stop type": payload.stop.type,
            "Risk per trade (%)": payload.risk.risk_fraction * 100,
            "Max position fraction (%)": payload.risk.max_position_fraction * 100,
            "WF train_days": payload.wf.train_days,
            "WF test_days": payload.wf.test_days,
            "WF step_days": payload.wf.step_days or payload.wf.test_days,
            "Min trades per window (test)": payload.wf.min_trades_test,
        },

        results=result,
    )

    public_url = f"/reports/strategies/{safe_symbol}/{filename}"
    return JSONResponse(content={"status": result.get("status", "ok"), "url": public_url})

@router.post("/run")
def run_strategies_async(
    payload: CalibrationStrategiesInspectRequest,
    storage: StorageManager = Depends(get_storage),
):
    import threading
    import uuid

    job_id = uuid.uuid4().hex

    WF_JOBS[job_id] = {
        "status": "running",
        "error": None,
        "progress": 0,
        "current_window": 0,
        "total_windows": 0,
        "current_strategy": None,
        "result": None,
    }

    def background_job():

        def progress_cb(window_id, total_windows):
            WF_JOBS[job_id]["current_window"] = window_id
            WF_JOBS[job_id]["total_windows"] = total_windows

            if total_windows and total_windows > 0:
                WF_JOBS[job_id]["progress"] = int(window_id / total_windows * 100)
            else:
                WF_JOBS[job_id]["progress"] = 0

        try:
            # 1) Cargar OHLCV (igual que /inspect y /validate)
            df = storage.load_ohlcv(symbol=payload.symbol, timeframe=payload.timeframe)
            if df is None or df.empty:
                WF_JOBS[job_id]["status"] = "error"
                WF_JOBS[job_id]["progress"] = 0
                WF_JOBS[job_id]["error"] = "No OHLCV data found. Run Step 1 first."
                return

            # 2) Calcular data_quality real
            from .calibration_data import analyze_data_quality
            data_quality = analyze_data_quality(df, payload.timeframe)

            # 3) Ejecutar con data_quality real
            result = inspect_strategies_config(
                storage=storage,
                payload=payload,
                data_quality=data_quality,
                include_series=True,
                progress_callback=progress_cb,
                df=df,
            )

            WF_JOBS[job_id]["status"] = "done"
            WF_JOBS[job_id]["progress"] = 100
            WF_JOBS[job_id]["result"] = result

        except Exception as e:
            logger.exception("❌ WF async job failed")
            WF_JOBS[job_id]["status"] = "error"
            WF_JOBS[job_id]["error"] = str(e)
            WF_JOBS[job_id]["progress"] = 0

    thread = threading.Thread(target=background_job)
    thread.start()

    return {"job_id": job_id}

@router.get("/status/{job_id}")
def get_status(job_id: str):
    return WF_JOBS.get(job_id, {"status": "unknown"})
