from __future__ import annotations

import json
import re
from datetime import date, timedelta
from typing import List, Optional, Tuple

import httpx

from app.config import settings
from app.models import FilterSpec, QueryIntent
from app.semantic import SemanticLayer


def _semantic_digest(semantic: SemanticLayer) -> str:
    lines: List[str] = []
    for name, ent in semantic.entities.items():
        lines.append(f"entity {name}: table={ent.physical_table}, time={ent.time_column}")
        lines.append("  metrics: " + ", ".join(f"{k} ({v.description})" for k, v in ent.metrics.items()))
        lines.append("  dimensions: " + ", ".join(f"{k} ({v.description})" for k, v in ent.dimensions.items()))
    return "\n".join(lines)


SYSTEM_PROMPT = """You are an analytics query interpreter. Convert the user's question into a single JSON object
matching this schema (no markdown fences):
{
  "entity": string,
  "metrics": string[],
  "dimensions": string[],
  "filters": [{"dimension": string, "op": "="|"!="|"in"|"between"|">"|"<"|">="|"<=", "value": any, "value_end": any}],
  "time_range": [string|null, string|null] | null,
  "order_by": string | null,
  "limit": number | null,
  "reasoning": string
}
Use only entity names, metric names, and dimension names from the SEMANTIC MODEL.
time_range uses inclusive ISO dates YYYY-MM-DD. If user says last quarter and no calendar given, use Q1 2025 as Jan 1 - Mar 31 2025 for demo data.
If the user refines a prior query, merge with PREVIOUS_INTENT when appropriate.
"""


async def interpret_nl_llm(
    user_message: str,
    semantic: SemanticLayer,
    prior: Optional[QueryIntent],
) -> QueryIntent:
    digest = _semantic_digest(semantic)
    prior_blob = prior.model_dump_json() if prior else "null"
    user_block = f"SEMANTIC MODEL:\n{digest}\n\nPREVIOUS_INTENT:\n{prior_blob}\n\nUSER:\n{user_message}"

    if not settings.openai_api_key:
        raise RuntimeError("OPENAI_API_KEY not set")

    payload = {
        "model": settings.openai_model,
        "messages": [
            {"role": "system", "content": SYSTEM_PROMPT},
            {"role": "user", "content": user_block},
        ],
        "temperature": 0.1,
        "response_format": {"type": "json_object"},
    }
    headers = {"Authorization": f"Bearer {settings.openai_api_key}"}
    async with httpx.AsyncClient(timeout=60.0) as client:
        r = await client.post(f"{settings.openai_base_url.rstrip('/')}/chat/completions", json=payload, headers=headers)
        r.raise_for_status()
        data = r.json()
        content = data["choices"][0]["message"]["content"]
    obj = json.loads(content)
    return QueryIntent.model_validate(obj)


def interpret_nl_heuristic(user_message: str, prior: Optional[QueryIntent]) -> QueryIntent:
    """Deterministic fallback when no LLM key is configured."""
    text = user_message.lower()
    base = prior.model_copy(deep=True) if prior else QueryIntent(entity="orders", metrics=["revenue"], dimensions=[])

    if ("order" in text and "count" in text) or "how many orders" in text:
        base.metrics = ["orders"]
    if "average" in text or "aov" in text:
        base.metrics = ["aov"]
    if "revenue" in text or "sales" in text:
        base.metrics = ["revenue"]

    dims: List[str] = []
    if "region" in text:
        dims.append("region")
    if "category" in text or "product" in text:
        dims.append("product_category")
    if "channel" in text:
        dims.append("channel")
    if dims:
        base.dimensions = list(dict.fromkeys(base.dimensions + dims))

    filters: List[FilterSpec] = list(base.filters)

    def add_eq(dim: str, val: str) -> None:
        filters.append(FilterSpec(dimension=dim, op="=", value=val))

    m = re.search(r"north america|emea|apac", text)
    if m:
        region = m.group(0).replace("north america", "North America").title()
        if region == "Emea":
            region = "EMEA"
        elif region == "Apac":
            region = "APAC"
        add_eq("region", region)

    if "web" in text:
        add_eq("channel", "Web")
    if "retail" in text:
        add_eq("channel", "Retail")

    # crude time ranges
    today = date(2025, 4, 30)  # align with seed data horizon
    start: Optional[str] = None
    end: Optional[str] = None
    if "q1" in text or "first quarter" in text:
        start, end = "2025-01-01", "2025-03-31"
    elif "january" in text:
        start, end = "2025-01-01", "2025-01-31"
    elif "february" in text:
        start, end = "2025-02-01", "2025-02-28"
    elif "march" in text:
        start, end = "2025-03-01", "2025-03-31"
    elif "last month" in text:
        first = (today.replace(day=1) - timedelta(days=1)).replace(day=1)
        last = today.replace(day=1) - timedelta(days=1)
        start, end = first.isoformat(), last.isoformat()
    elif "ytd" in text or "year to date" in text:
        start, end = "2025-01-01", today.isoformat()

    if start or end:
        base.time_range = (start, end)

    base.filters = filters
    base.reasoning = "Heuristic interpreter (no LLM): pattern matching on keywords and prior intent."
    if base.dimensions:
        base.order_by = base.metrics[0] if base.metrics else None
    return base


async def interpret_nl(
    user_message: str, semantic: SemanticLayer, prior: Optional[QueryIntent]
) -> Tuple[QueryIntent, bool]:
    if settings.openai_api_key:
        intent = await interpret_nl_llm(user_message, semantic, prior)
        return intent, True
    return interpret_nl_heuristic(user_message, prior), False
