"""
Pattern Lab router — historical backtest for pattern discovery.
Prefix: /api/pattern-lab
"""
import json
import uuid
from datetime import datetime
from typing import Optional, List

from fastapi import APIRouter, HTTPException, BackgroundTasks
from pydantic import BaseModel

from services.database import get_conn, get_config, save_custom_pattern

router = APIRouter(prefix="/api/pattern-lab", tags=["pattern-lab"])


# ── Pydantic models ────────────────────────────────────────────────────────────

class RunRequest(BaseModel):
    preset_id: Optional[str] = None
    theme: str
    analysis_date: str          # YYYY-MM-DD
    horizon_days: int = 90
    assets: List[str]
    theme_hint: str


class EvaluateRequest(BaseModel):
    pass


class SavePatternRequest(BaseModel):
    run_id: str
    pattern_index: int
    name: Optional[str] = None
    category: Optional[str] = None
    signal_direction: Optional[str] = None
    asset_class: Optional[str] = "indices"
    # Regime architecture
    action: Optional[str] = "new"        # "new" | "instance" | "counter"
    target_id: Optional[str] = None      # existing pattern id for instance/counter
    regime_tag: Optional[str] = None     # short generic label (e.g. "Health Crisis")
    event_name: Optional[str] = None     # human-readable event for historical_instance


class FindMatchingRequest(BaseModel):
    run_id: str
    pattern_index: int


class DiscoverRequest(BaseModel):
    query: str
    n: int = 6


class InstrumentScanRequest(BaseModel):
    ticker: str
    instrument_name: str
    start_date: str       # YYYY-MM-DD
    end_date: str         # YYYY-MM-DD
    horizon_days: int = 90


# ── Helpers ────────────────────────────────────────────────────────────────────

def _get_run(run_id: str) -> dict:
    conn = get_conn()
    row = conn.execute("SELECT * FROM backtest_lab_runs WHERE id=?", (run_id,)).fetchone()
    conn.close()
    if not row:
        raise HTTPException(404, "Run not found")
    return dict(row)


def _row_to_dict(row) -> dict:
    d = dict(row)
    for field in ("assets", "context_snapshot", "ai_result", "outcome"):
        if d.get(field) and isinstance(d[field], str):
            try:
                d[field] = json.loads(d[field])
            except Exception:
                pass
    return d


# ── Endpoints ──────────────────────────────────────────────────────────────────

@router.post("/run")
async def create_run(req: RunRequest):
    """Build historical context + run AI analysis. Returns run with AI patterns."""
    from services.pattern_lab import build_historical_context, run_ai_backtest

    openai_key = get_config("openai_api_key") or ""
    if not openai_key:
        raise HTTPException(400, "OpenAI API key not configured")

    run_id = f"LAB_{uuid.uuid4().hex[:8].upper()}"
    now    = datetime.utcnow().isoformat()

    # Persist run in pending state
    conn = get_conn()
    conn.execute("""INSERT INTO backtest_lab_runs
        (id, preset_id, theme, analysis_date, horizon_days, assets, theme_hint, status, created_at)
        VALUES (?,?,?,?,?,?,?,'running',?)""", (
        run_id, req.preset_id, req.theme, req.analysis_date,
        req.horizon_days, json.dumps(req.assets), req.theme_hint, now,
    ))
    conn.commit()
    conn.close()

    try:
        # Step 1: historical context
        context = build_historical_context(req.analysis_date, req.assets)

        # Step 2: AI analysis
        ai_result = await run_ai_backtest(context, req.theme_hint, req.horizon_days, openai_key)

        # Persist results
        conn = get_conn()
        conn.execute("""UPDATE backtest_lab_runs SET
            context_snapshot=?, ai_result=?, status='done'
            WHERE id=?""", (
            json.dumps(context), json.dumps(ai_result), run_id,
        ))
        conn.commit()
        conn.close()

        return {
            "run_id":   run_id,
            "status":   "done",
            "context":  context,
            "ai_result": ai_result,
        }

    except Exception as e:
        conn = get_conn()
        conn.execute("UPDATE backtest_lab_runs SET status='error', error_msg=? WHERE id=?",
                     (str(e), run_id))
        conn.commit()
        conn.close()
        raise HTTPException(500, f"Backtest failed: {e}")


@router.post("/evaluate/{run_id}")
def evaluate_run(run_id: str):
    """Fetch actual prices at T+horizon and score each AI pattern."""
    from services.pattern_lab import evaluate_outcomes

    run = _get_run(run_id)
    if run["status"] not in ("done", "evaluated"):
        raise HTTPException(400, f"Run status is '{run['status']}', must be 'done' first")

    outcomes = evaluate_outcomes(run)

    conn = get_conn()
    conn.execute("""UPDATE backtest_lab_runs SET
        outcome=?, status='evaluated', evaluated_at=datetime('now')
        WHERE id=?""", (json.dumps(outcomes), run_id))
    conn.commit()
    conn.close()

    return {"run_id": run_id, "outcomes": outcomes}


@router.get("/runs")
def list_runs():
    conn = get_conn()
    rows = conn.execute("""SELECT id, preset_id, theme, analysis_date, horizon_days,
        assets, status, created_at, evaluated_at,
        outcome, ai_result, context_snapshot
        FROM backtest_lab_runs ORDER BY created_at DESC LIMIT 100""").fetchall()
    conn.close()
    return [_row_to_dict(r) for r in rows]


@router.get("/runs/{run_id}")
def get_run(run_id: str):
    return _row_to_dict(_get_run(run_id))


@router.delete("/purge-all")
def purge_all_runs():
    """Delete all Pattern Lab runs."""
    conn = get_conn()
    conn.execute("DELETE FROM backtest_lab_runs")
    n = conn.total_changes
    conn.commit()
    conn.close()
    return {"deleted": n}


@router.delete("/runs/{run_id}")
def delete_run(run_id: str):
    conn = get_conn()
    conn.execute("DELETE FROM backtest_lab_runs WHERE id=?", (run_id,))
    conn.commit()
    conn.close()
    return {"deleted": run_id}


@router.post("/instrument-scan")
async def instrument_scan(req: InstrumentScanRequest):
    """Scan an instrument's historical price action to extract pattern instances."""
    from services.pattern_lab import run_instrument_scan

    openai_key = get_config("openai_api_key") or ""
    if not openai_key:
        raise HTTPException(400, "OpenAI API key not configured")

    run_id = f"LAB_INS_{uuid.uuid4().hex[:8].upper()}"
    now    = datetime.utcnow().isoformat()

    conn = get_conn()
    conn.execute("""INSERT INTO backtest_lab_runs
        (id, preset_id, theme, analysis_date, horizon_days, assets, theme_hint, status, created_at)
        VALUES (?,?,?,?,?,?,?,'running',?)""", (
        run_id,
        "instrument_scan",
        f"Instrument Scan: {req.instrument_name}",
        req.start_date,
        req.horizon_days,
        json.dumps([req.ticker]),
        json.dumps({"type": "instrument", "ticker": req.ticker,
                    "instrument_name": req.instrument_name,
                    "start_date": req.start_date, "end_date": req.end_date}),
        now,
    ))
    conn.commit()
    conn.close()

    try:
        ai_result = await run_instrument_scan(
            req.ticker, req.instrument_name,
            req.start_date, req.end_date,
            req.horizon_days, openai_key,
        )

        conn = get_conn()
        conn.execute("""UPDATE backtest_lab_runs SET
            ai_result=?, status='done'
            WHERE id=?""", (json.dumps(ai_result), run_id))
        conn.commit()
        conn.close()

        return {"run_id": run_id, "status": "done", "ai_result": ai_result}

    except Exception as e:
        conn = get_conn()
        conn.execute("UPDATE backtest_lab_runs SET status='error', error_msg=? WHERE id=?",
                     (str(e), run_id))
        conn.commit()
        conn.close()
        raise HTTPException(500, f"Instrument scan failed: {e}")


@router.post("/evaluate-instrument/{run_id}")
def evaluate_instrument_run(run_id: str):
    """Evaluate outcomes for an instrument scan (per-pattern dates)."""
    from services.pattern_lab import evaluate_instrument_outcomes

    run = _get_run(run_id)
    if run["status"] not in ("done", "evaluated"):
        raise HTTPException(400, f"Run status is '{run['status']}', must be 'done' first")

    outcomes = evaluate_instrument_outcomes(run)

    conn = get_conn()
    conn.execute("""UPDATE backtest_lab_runs SET
        outcome=?, status='evaluated', evaluated_at=datetime('now')
        WHERE id=?""", (json.dumps(outcomes), run_id))
    conn.commit()
    conn.close()

    return {"run_id": run_id, "outcomes": outcomes}


@router.post("/discover")
async def discover_events(req: DiscoverRequest):
    """Use GPT-4o to generate historical/recent event suggestions matching the query."""
    from openai import AsyncOpenAI
    key = get_config("openai_api_key") or ""
    if not key:
        raise HTTPException(400, "OpenAI API key not configured")
    client = AsyncOpenAI(api_key=key)

    prompt = f"""You are a financial markets research expert specialized in geopolitical and macro events.

Query: "{req.query}"

Identify {req.n} specific real events (2000-2025) that match this query and are relevant for options/derivatives trading analysis.
Prioritize events with clear market impact and dateable start points.

Return ONLY valid JSON:
{{
  "events": [
    {{
      "label": "Concise event name",
      "date": "YYYY-MM-DD",
      "category": "geopolitical|macro|volatility|commodities|fx_macro|credit",
      "horizon_days": 60,
      "assets": ["TICKER1", "TICKER2", "TICKER3"],
      "hint": "2-3 sentence market context for a trader: what happened, why it matters, key dynamics.",
      "confidence": 90
    }}
  ]
}}

Rules:
- Use valid Yahoo Finance tickers (SPY, GLD, USO, EURUSD=X, BTC-USD, etc.)
- horizon_days: typical holding period to capture the event's full market impact
- confidence: 0-100, how well-documented and dateable this event is
- Diversify dates and instruments across the {req.n} results"""

    resp = await client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": prompt}],
        response_format={"type": "json_object"},
        temperature=0.3,
        timeout=30,
    )
    result = json.loads(resp.choices[0].message.content)
    events = result.get("events", [])
    # Sort by confidence desc
    events.sort(key=lambda e: e.get("confidence", 0), reverse=True)
    return {"events": events, "source": "ai", "query": req.query}


@router.post("/find-matching")
async def find_matching_pattern(req: FindMatchingRequest):
    """Ask GPT-4o-mini if this lab pattern matches / counter-scenario an existing library pattern."""
    from services.database import get_custom_patterns
    from openai import AsyncOpenAI

    openai_key = get_config("openai_api_key") or ""
    if not openai_key:
        raise HTTPException(400, "OpenAI API key not configured")

    run = _get_run(req.run_id)
    ai_result = json.loads(run.get("ai_result") or "{}")
    patterns  = ai_result.get("patterns", [])
    if req.pattern_index >= len(patterns):
        raise HTTPException(400, "pattern_index out of range")

    pat = patterns[req.pattern_index]

    # Outcome for this pattern if evaluated
    outcome_list: list = []
    if run.get("outcome"):
        try:
            outcome_list = json.loads(run["outcome"]) if isinstance(run["outcome"], str) else run["outcome"]
        except Exception:
            pass
    outcome = next((o for o in outcome_list if o.get("pattern_name") == pat.get("name")), None)

    # Build compact library (id, name, category, underlying, direction, description, regime_tag)
    library = get_custom_patterns()
    if not library:
        return {
            "recommendation": "new_pattern", "match_id": None, "match_name": None,
            "confidence": 100, "reasoning": "Library is empty — save as new pattern.",
            "suggested_regime_tag": pat.get("category", ""),
        }

    lib_compact = []
    for p in library:
        trades = p.get("suggested_trades") or []
        underlying = trades[0].get("underlying", "") if trades else ""
        lib_compact.append({
            "id": p["id"],
            "name": p["name"],
            "regime_tag": p.get("regime_tag") or "",
            "category": p.get("category") or "",
            "underlying": underlying,
            "signal_direction": p.get("signal_direction") or "",
            "description": (p.get("description") or "")[:150],
        })

    outcome_str = "not evaluated"
    if outcome:
        ht = outcome.get("hit_type") or ("FULL HIT" if outcome.get("hit") else "MISS")
        outcome_str = f"{ht.upper()} — actual move {outcome.get('actual_move_pct',0):+.1f}%"

    prompt = f"""You are a quantitative macro pattern analyst. Determine if a new backtest pattern matches an existing library pattern.

## NEW PATTERN (from Pattern Lab backtest)
Name: {pat.get('name')}
Category: {pat.get('category')}
Description: {pat.get('description')}
Underlying: {pat.get('underlying')}
Strategy: {pat.get('strategy')}
Signal direction: {pat.get('signal_direction')} ({pat.get('expected_direction')})
Expected move: {pat.get('expected_direction','?')}{pat.get('expected_move_pct',0)}%
Backtest outcome: {outcome_str}

## EXISTING LIBRARY PATTERNS
{json.dumps(lib_compact, ensure_ascii=False)}

## DECISION RULES
**merge_as_instance**: Same macro REGIME + same INSTRUMENT + same DIRECTION (both bullish, or both bearish, or both volatility/neutral). The new event is just another historical proof of the same thesis.

**counter_scenario**: Same macro REGIME + same INSTRUMENT + OPPOSITE DIRECTION. Both are valid — they capture different scenarios within the same regime (e.g. pandemic bullish USD vs pandemic bearish USD).

**new_pattern**: Different regime, different instrument, or no meaningful structural match.

CRITICAL: Never merge opposite directions as a single pattern — use counter_scenario instead.
Suggest a short generic regime_tag (2-5 words) capturing the macro theme, not the specific event (e.g. "Health Crisis", "Fed Dovish Pivot", "EM Currency Stress", "Geopolitical Energy Shock").

Return ONLY valid JSON:
{{
  "recommendation": "merge_as_instance" | "counter_scenario" | "new_pattern",
  "match_id": "existing pattern id or null",
  "match_name": "existing pattern name or null",
  "confidence": 0-100,
  "reasoning": "2-3 sentences explaining the decision",
  "suggested_regime_tag": "short generic label"
}}"""

    client = AsyncOpenAI(api_key=openai_key)
    try:
        resp = await client.chat.completions.create(
            model="gpt-4o-mini",
            messages=[{"role": "user", "content": prompt}],
            temperature=0.1,
            response_format={"type": "json_object"},
            timeout=30,
        )
        return json.loads(resp.choices[0].message.content or "{}")
    except Exception as e:
        raise HTTPException(500, f"Matching failed: {e}")


@router.post("/save-pattern")
def save_pattern_from_run(req: SavePatternRequest):
    """Promote one AI-suggested pattern (with its outcome) to the pattern library."""
    run = _get_run(req.run_id)

    ai_result = json.loads(run.get("ai_result") or "{}")
    patterns  = ai_result.get("patterns", [])
    if req.pattern_index >= len(patterns):
        raise HTTPException(400, "pattern_index out of range")

    pat = patterns[req.pattern_index]
    outcome_list: list = []
    if run.get("outcome"):
        try:
            outcome_list = json.loads(run["outcome"]) if isinstance(run["outcome"], str) else run["outcome"]
        except Exception:
            pass

    # Match outcome for this pattern
    outcome = next((o for o in outcome_list if o.get("pattern_name") == pat.get("name")), None)
    hit = bool(outcome.get("hit")) if outcome else None
    actual_move = outcome.get("actual_move_pct") if outcome else None

    pat_name   = req.name or pat.get("name", "Unnamed Pattern")
    action     = req.action or "new"
    regime_tag = req.regime_tag or pat.get("category", "")

    new_instance = {
        "date":           run["analysis_date"],
        "event_name":     req.event_name or run["theme"],
        "horizon":        run["horizon_days"],
        "run_id":         req.run_id,
        "hit":            hit,
        "hit_type":       "full" if hit else "miss",
        "actual_move_pct": actual_move,
    }

    conn = get_conn()

    # ── ACTION: merge as historical instance of an existing pattern ────────────
    if action == "instance" and req.target_id:
        existing = conn.execute("SELECT * FROM custom_patterns WHERE id=?", (req.target_id,)).fetchone()
        if not existing:
            conn.close()
            raise HTTPException(404, "Target pattern not found")
        instances = json.loads(existing["historical_instances"] or "[]")
        instances.append(new_instance)
        new_hits = (existing["backtest_hits"] or 0) + (1 if hit else 0)
        new_runs = (existing["backtest_runs_count"] or 0) + 1
        conn.execute("""UPDATE custom_patterns SET
            historical_instances=?, backtest_hits=?, backtest_runs_count=?,
            regime_tag=COALESCE(regime_tag, ?), updated_at=datetime('now')
            WHERE id=?""", (json.dumps(instances), new_hits, new_runs, regime_tag, req.target_id))
        conn.commit()
        conn.close()
        return {"saved": req.target_id, "action": "merged_instance",
                "backtest_hits": new_hits, "backtest_runs_count": new_runs}

    # ── ACTION: counter-scenario (new pattern linked to existing) ─────────────
    # Also tag the parent with regime_tag if not already set
    if action == "counter" and req.target_id:
        conn.execute("""UPDATE custom_patterns SET
            regime_tag=COALESCE(NULLIF(regime_tag,''), ?)
            WHERE id=?""", (regime_tag, req.target_id))

    # ── ACTION: new or counter — check for exact name duplicate first ─────────
    name_match = conn.execute(
        "SELECT id, backtest_hits, backtest_runs_count FROM custom_patterns WHERE name=? AND source='backtested'",
        (pat_name,)
    ).fetchone()
    if name_match and action == "new":
        new_hits = (name_match["backtest_hits"] or 0) + (1 if hit else 0)
        new_runs = (name_match["backtest_runs_count"] or 0) + 1
        conn.execute("""UPDATE custom_patterns SET
            backtest_hits=?, backtest_runs_count=?,
            regime_tag=COALESCE(NULLIF(regime_tag,''), ?), updated_at=datetime('now')
            WHERE id=?""", (new_hits, new_runs, regime_tag, name_match["id"]))
        conn.commit()
        conn.close()
        return {"saved": name_match["id"], "action": "updated",
                "backtest_hits": new_hits, "backtest_runs_count": new_runs}

    # ── Create new pattern ────────────────────────────────────────────────────
    new_id = f"P_LAB_{uuid.uuid4().hex[:6].upper()}"
    conn.execute("""INSERT INTO custom_patterns (
        id, name, description, triggers, keywords, historical_instances,
        suggested_trades, asset_class, expected_move_pct, probability,
        horizon_days, source, category, signal_direction,
        backtest_hits, backtest_runs_count, regime_tag, counter_of,
        is_active, taxonomy_path, updated_at
    ) VALUES (?,?,?,?,?,?,?,?,?,?,?,'backtested',?,?,?,?,?,?,1,'[]',datetime('now'))""", (
        new_id,
        pat_name,
        pat.get("description", ""),
        json.dumps([]),
        json.dumps([]),
        json.dumps([new_instance]),
        json.dumps([{
            "underlying":        pat.get("underlying", ""),
            "strategy":          pat.get("strategy", ""),
            "expected_move_pct": pat.get("expected_move_pct", 0),
            "asset_class":       req.asset_class,
        }]),
        req.asset_class or "indices",
        pat.get("expected_move_pct", 0),
        pat.get("confidence", 50) / 100,
        pat.get("horizon_days", run["horizon_days"]),
        req.category or pat.get("category", ""),
        req.signal_direction or pat.get("signal_direction", ""),
        1 if hit else 0,
        1,
        regime_tag,
        req.target_id if action == "counter" else None,
    ))
    conn.commit()
    conn.close()

    return {"saved": new_id, "action": action,
            "backtest_hits": 1 if hit else 0, "backtest_runs_count": 1}
