from __future__ import annotations

import sqlite3
from contextlib import asynccontextmanager
from pathlib import Path

from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from fastapi.staticfiles import StaticFiles

from app.config import settings
from app.conversation import ConversationStore
from app.datasource import SqliteDataSource
from app.interpreter import interpret_nl
from app.charts import build_chart_payload
from app.models import ChatRequest, ChatResponse
from app.presenter import render_answer_markdown
from app.semantic import SemanticLayer
from app.sql_builder import SqlBuildError, build_sql

ROOT = Path(__file__).resolve().parent.parent
STATIC = ROOT / "static"
SEED_SQL = ROOT / "data" / "seed.sql"


def ensure_database() -> None:
    settings.sqlite_path.parent.mkdir(parents=True, exist_ok=True)
    if settings.sqlite_path.exists():
        return
    if not SEED_SQL.is_file():
        raise RuntimeError(f"Missing seed SQL at {SEED_SQL}")
    conn = sqlite3.connect(settings.sqlite_path)
    conn.executescript(SEED_SQL.read_text(encoding="utf-8"))
    conn.commit()
    conn.close()


semantic: SemanticLayer | None = None
store: ConversationStore | None = None
source: SqliteDataSource | None = None


@asynccontextmanager
async def lifespan(app: FastAPI):
    ensure_database()
    global semantic, store, source
    semantic = SemanticLayer.load()
    store = ConversationStore()
    source = SqliteDataSource(settings.sqlite_path)
    yield


app = FastAPI(title="Conversational Analytics PoC", lifespan=lifespan)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)


@app.get("/api/health")
def health():
    return {"status": "ok", "sqlite": str(settings.sqlite_path), "llm": bool(settings.openai_api_key)}


@app.post("/api/chat", response_model=ChatResponse)
async def chat(req: ChatRequest):
    assert semantic is not None and store is not None and source is not None
    sid = req.session_id or store.new_session()
    if req.session_id and not store.has_session(req.session_id):
        raise HTTPException(status_code=404, detail="Unknown session_id")

    store.append(sid, "user", req.message)
    prior = store.get_last_intent(sid)
    try:
        intent, used_llm = await interpret_nl(req.message, semantic, prior)
        sql, params = build_sql(semantic, source, intent)
        result = source.execute(sql, params)
    except SqlBuildError as e:
        raise HTTPException(status_code=400, detail=str(e)) from e
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e)) from e

    answer = render_answer_markdown(intent, result)
    chart = build_chart_payload(intent, result)
    store.append(sid, "assistant", answer)
    store.set_last_intent(sid, intent)
    return ChatResponse(
        session_id=sid,
        answer_markdown=answer,
        intent=intent,
        execution=result,
        used_llm=used_llm,
        chart=chart,
    )


app.mount("/static", StaticFiles(directory=str(STATIC)), name="static")


@app.get("/")
def index():
    return FileResponse(str(STATIC / "index.html"))
