"""
Lead Flow Simulate — SSE endpoint cho Interactive Logic Flow Simulator.

Nhận query text → Chạy Classifier → Stream từng tier cascade search qua SSE.
Frontend nhận events để animate từng node trên sơ đồ logic.
"""

import asyncio
import json
import logging
import time

from fastapi import APIRouter, Query
from fastapi.responses import StreamingResponse

from agent.lead_stage_agent.product_search_engine import (LeadSearchInput, _build_exclusion_clauses,
                                                          _build_fixed_clauses, _build_full_query, _build_search_clause,
                                                          _build_tag_clauses, _enrich_with_stock)
from common.starrocks_connection import get_db_connection

logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/lead-flow", tags=["Lead Flow Simulate"])


def _sse_event(event: str, data: dict) -> str:
    """Format SSE event string."""
    payload = json.dumps(data, ensure_ascii=False, default=str)
    return f"event: {event}\ndata: {payload}\n\n"


async def _run_tier_query(db, sql: str, params: tuple) -> list:
    """Execute a single tier query safely."""
    try:
        return await db.execute_query_async(sql, params=params)
    except Exception as e:
        logger.error("Tier query error: %s", e)
        return []


async def _simulate_stream(query: str):
    """Generator that yields SSE events for each step of the search pipeline."""

    t_global = time.time()

    # ── Step 0: Start ──
    yield _sse_event("start", {"query": query, "timestamp": time.time()})
    await asyncio.sleep(0.05)

    # ── Step 1: Classifier ──
    yield _sse_event("status", {"step": "classifier", "state": "running"})

    t0 = time.time()
    try:
        from langchain_core.messages import HumanMessage, SystemMessage

        from agent.lead_stage_agent.graph import ClassifierOutput
        from agent.lead_stage_agent.prompts import LEAD_STAGE_CLASSIFIER_PROMPT
        from common.llm_factory import create_llm
        from config import DEFAULT_MODEL

        _base = create_llm(model_name=DEFAULT_MODEL, streaming=False)
        classifier_llm = _base.with_structured_output(ClassifierOutput, method="function_calling")

        classifier_messages = [
            SystemMessage(content=LEAD_STAGE_CLASSIFIER_PROMPT),
            HumanMessage(content=f"=== TIN NHẮN HIỆN TẠI ===\n{query}\n\n=== USER INSIGHT ===\nChưa có thông tin.\n\n=== LỊCH SỬ ===\nChưa có lịch sử."),
        ]

        output: ClassifierOutput = await classifier_llm.ainvoke(classifier_messages)
        classifier_ms = round((time.time() - t0) * 1000)

        # Parse args
        if output.tool_name == "lead_search_tool" and output.lead_search_args:
            args_dict = output.lead_search_args.model_dump(exclude_none=False)
        elif output.tool_name == "lead_search_tool" and output.tool_args:
            args_dict = output.tool_args
        else:
            args_dict = {}

        classifier_data = {
            "reasoning": output.reasoning,
            "tool_name": output.tool_name,
            "ai_response": output.ai_response,
            "args": args_dict,
            "elapsed_ms": classifier_ms,
        }

        # Early exit
        if not output.tool_name:
            yield _sse_event("classifier", {**classifier_data, "early_exit": True})
            yield _sse_event("done", {
                "early_exit": True,
                "ai_response": output.ai_response,
                "total_ms": round((time.time() - t_global) * 1000),
            })
            return

        yield _sse_event("classifier", {**classifier_data, "early_exit": False})
        await asyncio.sleep(0.05)

    except Exception as e:
        logger.error("Classifier error: %s", e, exc_info=True)
        yield _sse_event("classifier", {
            "error": str(e)[:300],
            "elapsed_ms": round((time.time() - t0) * 1000),
        })
        yield _sse_event("done", {"error": True, "total_ms": round((time.time() - t_global) * 1000)})
        return

    # ── Step 2: Build LeadSearchInput ──
    try:
        req = LeadSearchInput(**{k: v for k, v in args_dict.items() if k in LeadSearchInput.model_fields})
    except Exception:
        req = LeadSearchInput()

    yield _sse_event("status", {"step": "cascade", "state": "running"})
    await asyncio.sleep(0.05)

    # ── Step 3: Cascade search — ALL tiers ──
    db = get_db_connection()

    ex_params_base = []
    exclusions = _build_exclusion_clauses(req.keywords, ex_params_base)

    hit_tier = None
    hit_products = []

    # Tier 1: Fixed + Keywords
    t1 = time.time()
    tier1_products = []
    tier1_sql = ""
    if req.keywords:
        params = []
        fixed = _build_fixed_clauses(req, params)
        search = _build_search_clause(req.keywords, params)
        if search:
            tier1_sql = _build_full_query(fixed, search, exclusions)
            tier1_products = await _run_tier_query(db, tier1_sql, tuple(params + ex_params_base))

    tier1_ms = round((time.time() - t1) * 1000)
    tier1_hit = len(tier1_products) > 0
    if tier1_hit and hit_tier is None:
        hit_tier = 1
        hit_products = tier1_products

    yield _sse_event("tier", {
        "tier": 1,
        "label": "Fixed + Keywords (NGRAMBF)",
        "count": len(tier1_products),
        "hit": tier1_hit,
        "is_winner": hit_tier == 1,
        "elapsed_ms": tier1_ms,
        "products": [{"name": p.get("product_name", ""), "sku": p.get("internal_ref_code", ""), "price": p.get("sale_price", 0)} for p in tier1_products[:5]],
        "filters_used": {"keywords": req.keywords},
    })
    await asyncio.sleep(0.1)

    # Tier 2: Fixed + Tags
    t2 = time.time()
    tier2_products = []
    if req.tags:
        params = []
        fixed = _build_fixed_clauses(req, params)
        search = _build_tag_clauses(req.tags, params)
        if search:
            sql = _build_full_query(fixed, search, exclusions)
            tier2_products = await _run_tier_query(db, sql, tuple(params + ex_params_base))

    tier2_ms = round((time.time() - t2) * 1000)
    tier2_hit = len(tier2_products) > 0
    if tier2_hit and hit_tier is None:
        hit_tier = 2
        hit_products = tier2_products

    yield _sse_event("tier", {
        "tier": 2,
        "label": "Fixed + Tags (BITMAP)",
        "count": len(tier2_products),
        "hit": tier2_hit,
        "is_winner": hit_tier == 2,
        "elapsed_ms": tier2_ms,
        "products": [{"name": p.get("product_name", ""), "sku": p.get("internal_ref_code", ""), "price": p.get("sale_price", 0)} for p in tier2_products[:5]],
        "filters_used": {"tags": req.tags},
    })
    await asyncio.sleep(0.1)

    # Tier 3: Fixed Only
    t3 = time.time()
    params = []
    fixed = _build_fixed_clauses(req, params)
    sql = _build_full_query(fixed, None, exclusions)
    tier3_products = await _run_tier_query(db, sql, tuple(params + ex_params_base))
    tier3_ms = round((time.time() - t3) * 1000)
    tier3_hit = len(tier3_products) > 0
    if tier3_hit and hit_tier is None:
        hit_tier = 3
        hit_products = tier3_products

    yield _sse_event("tier", {
        "tier": 3,
        "label": "Fixed Only (type+color+gender+price)",
        "count": len(tier3_products),
        "hit": tier3_hit,
        "is_winner": hit_tier == 3,
        "elapsed_ms": tier3_ms,
        "products": [{"name": p.get("product_name", ""), "sku": p.get("internal_ref_code", ""), "price": p.get("sale_price", 0)} for p in tier3_products[:5]],
        "filters_used": {"fixed_only": True},
    })
    await asyncio.sleep(0.1)

    # Tier 4: Drop gender
    t4 = time.time()
    tier4_products = []
    if req.product_line_vn and req.gender_by_product:
        saved = req.gender_by_product
        req.gender_by_product = None
        params = []
        fixed = _build_fixed_clauses(req, params)
        sql = _build_full_query(fixed, None, exclusions)
        tier4_products = await _run_tier_query(db, sql, tuple(params + ex_params_base))
        req.gender_by_product = saved

    tier4_ms = round((time.time() - t4) * 1000)
    tier4_hit = len(tier4_products) > 0
    if tier4_hit and hit_tier is None:
        hit_tier = 4
        hit_products = tier4_products

    yield _sse_event("tier", {
        "tier": 4,
        "label": "Drop Gender",
        "count": len(tier4_products),
        "hit": tier4_hit,
        "is_winner": hit_tier == 4,
        "elapsed_ms": tier4_ms,
        "products": [{"name": p.get("product_name", ""), "sku": p.get("internal_ref_code", ""), "price": p.get("sale_price", 0)} for p in tier4_products[:5]],
        "filters_used": {"dropped": "gender_by_product"},
    })
    await asyncio.sleep(0.1)

    # Tier 5: Price ×1.5
    t5 = time.time()
    tier5_products = []
    if req.price_max is not None and req.product_line_vn:
        saved_max = req.price_max
        saved_min = req.price_min
        req.price_max = int(req.price_max * 1.5)
        req.price_min = None
        params = []
        fixed = _build_fixed_clauses(req, params)
        sql = _build_full_query(fixed, None)
        tier5_products = await _run_tier_query(db, sql, tuple(params))
        req.price_max = saved_max
        req.price_min = saved_min

    tier5_ms = round((time.time() - t5) * 1000)
    tier5_hit = len(tier5_products) > 0
    if tier5_hit and hit_tier is None:
        hit_tier = 5
        hit_products = tier5_products

    yield _sse_event("tier", {
        "tier": 5,
        "label": "Price Relaxation ×1.5",
        "count": len(tier5_products),
        "hit": tier5_hit,
        "is_winner": hit_tier == 5,
        "elapsed_ms": tier5_ms,
        "products": [{"name": p.get("product_name", ""), "sku": p.get("internal_ref_code", ""), "price": p.get("sale_price", 0)} for p in tier5_products[:5]],
        "filters_used": {"price_max_relaxed": int((req.price_max or 0) * 1.5)},
    })
    await asyncio.sleep(0.1)

    # Tier 6: Price ×2.0
    t6 = time.time()
    tier6_products = []
    if req.price_max is not None and req.product_line_vn:
        saved_max = req.price_max
        saved_min = req.price_min
        req.price_max = int(req.price_max * 2.0)
        req.price_min = None
        params = []
        fixed = _build_fixed_clauses(req, params)
        sql = _build_full_query(fixed, None)
        tier6_products = await _run_tier_query(db, sql, tuple(params))
        req.price_max = saved_max
        req.price_min = saved_min

    tier6_ms = round((time.time() - t6) * 1000)
    tier6_hit = len(tier6_products) > 0
    if tier6_hit and hit_tier is None:
        hit_tier = 6
        hit_products = tier6_products

    yield _sse_event("tier", {
        "tier": 6,
        "label": "Price Relaxation ×2.0",
        "count": len(tier6_products),
        "hit": tier6_hit,
        "is_winner": hit_tier == 6,
        "elapsed_ms": tier6_ms,
        "products": [{"name": p.get("product_name", ""), "sku": p.get("internal_ref_code", ""), "price": p.get("sale_price", 0)} for p in tier6_products[:5]],
        "filters_used": {"price_max_relaxed": int((req.price_max or 0) * 2.0)},
    })
    await asyncio.sleep(0.1)

    # Tier 7: Drop price
    t7 = time.time()
    tier7_products = []
    if req.price_max is not None:
        saved_max = req.price_max
        saved_min = req.price_min
        req.price_max = None
        req.price_min = None
        params = []
        fixed = _build_fixed_clauses(req, params)
        sql = _build_full_query(fixed, None, exclusions)
        tier7_products = await _run_tier_query(db, sql, tuple(params + ex_params_base))
        req.price_max = saved_max
        req.price_min = saved_min

    tier7_ms = round((time.time() - t7) * 1000)
    tier7_hit = len(tier7_products) > 0
    if tier7_hit and hit_tier is None:
        hit_tier = 7
        hit_products = tier7_products

    yield _sse_event("tier", {
        "tier": 7,
        "label": "Drop Price (no price filter)",
        "count": len(tier7_products),
        "hit": tier7_hit,
        "is_winner": hit_tier == 7,
        "elapsed_ms": tier7_ms,
        "products": [{"name": p.get("product_name", ""), "sku": p.get("internal_ref_code", ""), "price": p.get("sale_price", 0)} for p in tier7_products[:5]],
        "filters_used": {"dropped": "price_min, price_max"},
    })
    await asyncio.sleep(0.1)

    # ── Step 4: Stock Enrichment ──
    yield _sse_event("status", {"step": "stock", "state": "running"})
    stock_data = {"in_stock": 0, "dropped": 0, "timed_out": False, "elapsed_ms": 0}

    if hit_products:
        try:
            in_stock_list, filtered, stock_ms, timed_out = await _enrich_with_stock(hit_products[:10])
            stock_data = {
                "in_stock": len(in_stock_list),
                "dropped": len(hit_products[:10]) - len(in_stock_list),
                "timed_out": timed_out,
                "elapsed_ms": round(stock_ms),
            }
        except Exception as e:
            stock_data["error"] = str(e)[:200]

    yield _sse_event("stock", stock_data)
    await asyncio.sleep(0.05)

    # ── Done ──
    total_ms = round((time.time() - t_global) * 1000)
    yield _sse_event("done", {
        "hit_tier": hit_tier,
        "total_products": len(hit_products),
        "total_ms": total_ms,
    })


@router.get("/simulate", summary="SSE Simulate Lead Search Pipeline")
async def simulate_lead_search(query: str = Query(..., description="User query text")):
    """
    SSE endpoint — stream từng bước của lead search pipeline.
    Frontend dùng EventSource để nhận events realtime.
    """
    return StreamingResponse(
        _simulate_stream(query),
        media_type="text/event-stream",
        headers={
            "Cache-Control": "no-cache",
            "Connection": "keep-alive",
            "X-Accel-Buffering": "no",
        },
    )
