from __future__ import annotations

import os
import sys
import asyncio
import time
from contextlib import asynccontextmanager
from typing import Any

from datetime import datetime

import uvicorn
from dotenv import load_dotenv
from fastapi import FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import subprocess
from pydantic import BaseModel

try:
    from models.basketball_v25 import get_basketball_v25_predictor
    HAS_BASKETBALL = True
except ImportError:
    HAS_BASKETBALL = False
from services.single_match_orchestrator import get_single_match_orchestrator
from services.v26_shadow_engine import get_v26_shadow_engine
from models.league_model import get_league_model_loader

load_dotenv()

if sys.stdout and hasattr(sys.stdout, "reconfigure"):
    sys.stdout.reconfigure(encoding="utf-8")
if sys.stderr and hasattr(sys.stderr, "reconfigure"):
    sys.stderr.reconfigure(encoding="utf-8")


class CouponRequest(BaseModel):
    match_ids: list[str]
    strategy: str | None = "BALANCED"
    max_matches: int | None = None
    min_confidence: float | None = None


class RetrainRequest(BaseModel):
    reason: str | None = "manual"
    markets: str | None = None  # comma-separated, e.g. "MS,OU25,BTTS"
    trials: int | None = 50


# ─── Retrain state tracking ──────────────────────────────────
_retrain_state: dict[str, Any] = {
    "running": False,
    "last_started": None,
    "last_completed": None,
    "last_status": None,
    "last_error": None,
    "pid": None,
}


@asynccontextmanager
async def lifespan(_: FastAPI):
    try:
        print("🚀 Initializing V28 orchestrator...", flush=True)
        get_single_match_orchestrator()
        get_v26_shadow_engine()
        print("✅ V28 orchestrator ready", flush=True)
    except Exception as error:
        print(f"❌ Failed to initialize orchestrator: {error}", flush=True)
        import traceback

        traceback.print_exc()

    yield


app = FastAPI(
    title="Suggest-Bet AI Engine",
    version="28.0.0",
    description="V28 Single Match Prediction Package API",
    lifespan=lifespan,
)


def _parse_cors_origins() -> list[str]:
    raw = os.getenv("CORS_ALLOW_ORIGINS", "").strip()
    if raw:
        return [item.strip() for item in raw.split(",") if item.strip()]
    # Dev-safe defaults + production domains.
    return [
        "http://localhost:3000",
        "http://127.0.0.1:3000",
        "http://localhost:3001",
        "http://127.0.0.1:3001",
        "http://localhost:3005",
        "http://127.0.0.1:3005",
        "https://ui-suggestbet.bilgich.com",
        "https://suggestbet.bilgich.com",
        "https://iddaai.com",
        "https://www.iddaai.com",
    ]


app.add_middleware(
    CORSMiddleware,
    allow_origins=_parse_cors_origins(),
    allow_origin_regex=r"^https?://(localhost|127\.0\.0\.1)(:\d+)?$",
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.exception_handler(Exception)
async def global_exception_handler(_: Request, exc: Exception):
    import traceback

    print(f"💥 ERROR: {exc}", flush=True)
    traceback.print_exc()
    return JSONResponse(
        status_code=500,
        content={"message": f"Internal Server Error: {str(exc)}"},
    )


@app.get("/")
def read_root() -> dict[str, Any]:
    return {
        "status": "Suggest-Bet AI Engine v28",
        "engine": "V28 Single Match Orchestrator",
        "mode": os.getenv("AI_ENGINE_MODE", "v28"),
        "routes": [
            "POST /v20plus/analyze/{match_id}",
            "GET /v20plus/analyze-htms/{match_id}",
            "GET /v20plus/analyze-htft/{match_id}",
            "GET /v20plus/reversal-watchlist",
            "POST /v20plus/coupon",
            "GET /v20plus/daily-banker",
            "POST /v1/admin/retrain",
            "GET /v1/admin/retrain/status",
        ],
    }


@app.get("/health")
def health_check() -> dict[str, Any]:
    try:
        orchestrator = get_single_match_orchestrator()
        shadow_engine = get_v26_shadow_engine()

        # Per-market V25 model status
        v25_readiness: dict[str, Any] = {"fully_loaded": False}
        try:
            v25_predictor = orchestrator._get_v25_predictor()
            v25_readiness = v25_predictor.readiness_summary()
        except Exception as v25_err:
            v25_readiness = {"fully_loaded": False, "error": str(v25_err)}

        if HAS_BASKETBALL:
            basketball_predictor = get_basketball_v25_predictor()
            basketball_readiness = basketball_predictor.readiness_summary()
            ready = bool(basketball_readiness.get("fully_loaded", True))
        else:
            basketball_readiness = {"fully_loaded": False, "error": "Basketball module not found"}
            ready = True

        league_readiness = get_league_model_loader().readiness_summary()
        overall_ready = ready and v25_readiness.get("fully_loaded", False)
        return {
            "status": "healthy" if overall_ready else "degraded",
            "engine": "v28.main",
            "mode": os.getenv("AI_ENGINE_MODE", "v28"),
            "ready": overall_ready,
            "v25_football": v25_readiness,
            "league_specific": league_readiness,
            "basketball_v25": basketball_readiness,
            "v26_shadow": shadow_engine.readiness_summary(),
            "prediction_service_ready": True,
            "model_loaded": overall_ready,
            "orchestrator_mode": getattr(orchestrator, "engine_mode", "v28"),
        }
    except Exception as error:
        return {"status": "unhealthy", "ready": False, "error": str(error)}


_REQUIRED_RESPONSE_FIELDS = ("match_info", "market_board", "main_pick", "bet_summary", "data_quality")


@app.post("/v20plus/analyze/{match_id}")
async def analyze_match_v20plus(match_id: str) -> dict[str, Any]:
    started_at = time.time()
    orchestrator = get_single_match_orchestrator()
    result = await asyncio.to_thread(orchestrator.analyze_match, match_id)
    elapsed_ms = int((time.time() - started_at) * 1000)

    if not result:
        raise HTTPException(status_code=404, detail=f"Match not found: {match_id}")

    # Response validation: log missing required fields (non-fatal)
    missing_fields = [f for f in _REQUIRED_RESPONSE_FIELDS if f not in result]
    if missing_fields:
        print(f"⚠️ [API] analyze/{match_id} response missing fields: {missing_fields} ({elapsed_ms}ms)")

    result["timing_ms"] = elapsed_ms
    return result


@app.get("/v20plus/analyze-htms/{match_id}")
async def analyze_match_htms_v20plus(match_id: str) -> dict[str, Any]:
    orchestrator = get_single_match_orchestrator()
    result = await asyncio.to_thread(orchestrator.analyze_match_htms, match_id)
    if not result:
        raise HTTPException(status_code=404, detail=f"Match not found: {match_id}")
    return result


@app.get("/v20plus/analyze-htft/{match_id}")
async def analyze_match_htft_v20plus(match_id: str, timeout_sec: int = 30) -> dict[str, Any]:
    # Small, explicit endpoint for HT/FT inspection and debugging in FE/Postman.
    if timeout_sec < 3 or timeout_sec > 120:
        raise HTTPException(status_code=400, detail="timeout_sec must be between 3 and 120")

    orchestrator = get_single_match_orchestrator()
    started_at = time.time()

    try:
        result = await asyncio.wait_for(
            asyncio.to_thread(orchestrator.analyze_match, match_id),
            timeout=float(timeout_sec),
        )
    except asyncio.TimeoutError as error:
        raise HTTPException(
            status_code=504,
            detail=f"Analyze timeout after {timeout_sec}s for match_id={match_id}",
        ) from error

    if not result:
        raise HTTPException(status_code=404, detail=f"Match not found: {match_id}")

    risk = result.get("risk", {})
    market_board = result.get("market_board", {})
    htft_probs = market_board.get("HTFT", {}).get("probs", {}) or risk.get("ht_ft_probs", {})
    top_reversal_pick = None
    top_reversal_prob = 0.0
    if htft_probs:
        prob_12 = float(htft_probs.get("1/2", 0.0))
        prob_21 = float(htft_probs.get("2/1", 0.0))
        if prob_21 >= prob_12:
            top_reversal_pick = "2/1"
            top_reversal_prob = prob_21
        else:
            top_reversal_pick = "1/2"
            top_reversal_prob = prob_12

    overall_htft_pick = None
    overall_htft_prob = 0.0
    if htft_probs:
        overall_htft_pick, overall_htft_prob = max(
            htft_probs.items(),
            key=lambda item: float(item[1]),
        )
    return {
        "engine": "v28.main",
        "match_info": result.get("match_info", {}),
        "timing_ms": int((time.time() - started_at) * 1000),
        "ht_ft_probs": htft_probs,
        "top_reversal_pick": top_reversal_pick,
        "top_reversal_prob": round(float(top_reversal_prob), 4),
        "overall_htft_pick": overall_htft_pick,
        "overall_htft_pick_prob": round(float(overall_htft_prob), 4),
        "surprise_hunter": result.get("surprise_hunter", {}),
        "ht_ft_reversal_radar": result.get("ht_ft_reversal_radar", {}),
        "first_half_result": result.get("market_board", {}).get("first_half_result", {}),
        "main_pick": result.get("main_pick", {}),
        "bet_summary": result.get("bet_summary", {}),
    }


@app.post("/v20plus/coupon")
async def generate_coupon_v20plus(request: CouponRequest) -> dict[str, Any]:
    orchestrator = get_single_match_orchestrator()
    return await asyncio.to_thread(
        orchestrator.build_coupon,
        request.match_ids,
        request.strategy or "BALANCED",
        request.max_matches,
        request.min_confidence,
    )


@app.get("/v20plus/daily-banker")
async def get_daily_banker_v20plus(count: int = 3) -> dict[str, Any]:
    if count < 1:
        raise HTTPException(status_code=400, detail="count must be >= 1")

    orchestrator = get_single_match_orchestrator()
    bankers = await asyncio.to_thread(orchestrator.get_daily_bankers, count)
    return {"count": len(bankers), "bankers": bankers}

@app.get("/v20plus/reversal-watchlist")
async def get_reversal_watchlist_v20plus(
    count: int = 20,
    horizon_hours: int = 72,
    min_score: float = 45.0,
    top_leagues_only: bool = False,
) -> dict[str, Any]:
    if count < 1 or count > 100:
        raise HTTPException(status_code=400, detail="count must be between 1 and 100")
    if horizon_hours < 6 or horizon_hours > 168:
        raise HTTPException(status_code=400, detail="horizon_hours must be between 6 and 168")
    if min_score < 0 or min_score > 100:
        raise HTTPException(status_code=400, detail="min_score must be between 0 and 100")

    orchestrator = get_single_match_orchestrator()
    return await asyncio.to_thread(
        orchestrator.get_reversal_watchlist,
        count,
        horizon_hours,
        min_score,
        top_leagues_only,
    )


# ─── ADMIN: Retrain Pipeline ─────────────────────────────────

def _run_retrain_pipeline(markets: str | None, trials: int):
    """Background function: extract data → train model → reload."""
    global _retrain_state
    ai_dir = os.path.dirname(os.path.abspath(__file__))
    scripts_dir = os.path.join(ai_dir, "scripts")
    python = os.path.join(ai_dir, "venv", "bin", "python3")
    if not os.path.exists(python):
        python = sys.executable  # fallback

    try:
        # Step 1: Extract training data
        print("🔄 [RETRAIN] Step 1/3: Extracting training data...", flush=True)
        result = subprocess.run(
            [python, os.path.join(scripts_dir, "extract_training_data.py")],
            capture_output=True, text=True, timeout=600, cwd=ai_dir,
        )
        if result.returncode != 0:
            raise RuntimeError(f"Extract failed:\n{result.stderr[-500:]}")
        print(f"✅ [RETRAIN] Extract done", flush=True)

        # Step 2: Train V25 Pro
        print("🔄 [RETRAIN] Step 2/3: Training V25 Pro model...", flush=True)
        train_cmd = [python, os.path.join(scripts_dir, "train_v25_pro.py")]
        if markets:
            train_cmd += ["--markets", markets]
        train_cmd += ["--trials", str(trials)]

        result = subprocess.run(
            train_cmd, capture_output=True, text=True, timeout=3600, cwd=ai_dir,
        )
        if result.returncode != 0:
            raise RuntimeError(f"Training failed:\n{result.stderr[-500:]}")
        print(f"✅ [RETRAIN] Training done", flush=True)

        # Step 3: Reload models in memory
        print("🔄 [RETRAIN] Step 3/3: Reloading models...", flush=True)
        try:
            orchestrator = get_single_match_orchestrator()
            v25 = orchestrator._get_v25_predictor()
            v25._loaded = False
            v25.load_models()
            print("✅ [RETRAIN] Models reloaded in memory", flush=True)
        except Exception as reload_err:
            print(f"⚠️ [RETRAIN] Hot reload failed (restart needed): {reload_err}", flush=True)

        _retrain_state.update({
            "running": False,
            "last_completed": datetime.now().isoformat(),
            "last_status": "success",
            "last_error": None,
        })
        print("🎉 [RETRAIN] Pipeline complete!", flush=True)

    except Exception as err:
        _retrain_state.update({
            "running": False,
            "last_completed": datetime.now().isoformat(),
            "last_status": "failed",
            "last_error": str(err),
        })
        print(f"❌ [RETRAIN] Pipeline failed: {err}", flush=True)


@app.post("/v1/admin/retrain")
async def admin_retrain(request: RetrainRequest) -> dict[str, Any]:
    """Trigger full retrain pipeline: extract → train → reload."""
    if _retrain_state["running"]:
        return {
            "status": "already_running",
            "message": f"Retrain in progress since {_retrain_state['last_started']}",
        }

    _retrain_state.update({
        "running": True,
        "last_started": datetime.now().isoformat(),
        "last_status": "running",
        "last_error": None,
    })

    # Run in background thread
    import threading
    thread = threading.Thread(
        target=_run_retrain_pipeline,
        args=(request.markets, request.trials or 50),
        daemon=True,
    )
    thread.start()

    return {
        "status": "triggered",
        "message": "Retrain pipeline started in background",
        "reason": request.reason,
        "markets": request.markets or "all",
        "trials": request.trials or 50,
    }


@app.get("/v1/admin/retrain/status")
async def admin_retrain_status() -> dict[str, Any]:
    """Check retrain pipeline status."""
    return {**_retrain_state}


if __name__ == "__main__":
    port = int(os.getenv("PORT", "8000"))
    uvicorn.run("main:app", host="0.0.0.0", port=port, reload=True)
