from __future__ import annotations

import asyncio
import json
import os
from collections import Counter
from typing import Optional

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

router = APIRouter(prefix="/dataset")


# ── Config ─────────────────────────────────────────────────────────────────────

FLAG_THRESHOLD = 0.7
HIGH_THRESHOLD = 0.9

_SYSTEM_PROMPT = (
    "You are a synthetic-text detector. Given a text, return a JSON object with a single "
    "key 'synthetic_score' (float 0.0–1.0). 1.0 means almost certainly AI-generated, "
    "0.0 means almost certainly human-written. Return only valid JSON, no explanation."
)


# ── OpenAI client (lazy) ───────────────────────────────────────────────────────

_client: Optional[openai.AsyncOpenAI] = None

def _get_client() -> openai.AsyncOpenAI:
    global _client
    if _client is None:
        api_key = os.environ.get("OPENAI_API_KEY")
        if not api_key:
            raise RuntimeError("OPENAI_API_KEY not set — synthetic detection unavailable.")
        _client = openai.AsyncOpenAI(api_key=api_key)
    return _client


async def _score_text(text: str) -> float:
    client = _get_client()
    try:
        resp = await client.chat.completions.create(
            model="gpt-4o-mini",
            temperature=0,
            response_format={"type": "json_object"},
            messages=[
                {"role": "system", "content": _SYSTEM_PROMPT},
                {"role": "user", "content": text[:1000]},
            ],
        )
        data = json.loads(resp.choices[0].message.content)
        return float(data.get("synthetic_score", 0.0))
    except Exception:
        return 0.0


# ── Core ───────────────────────────────────────────────────────────────────────

async def run_synthetic_analysis(
    rows: list[dict],
    text_columns: list[str],
    max_rows: int = 300,
    concurrency: int = 20,
    flag_threshold: float = FLAG_THRESHOLD,
    high_threshold: float = HIGH_THRESHOLD,
) -> dict:
    if not text_columns:
        return {"error": "No text columns for synthetic detection"}

    try:
        _get_client()
    except RuntimeError as e:
        return {"error": str(e)}

    sample = rows[:max_rows]
    total_rows = len(sample)

    col_synthetic_scores: dict[str, list[float]] = {c: [] for c in text_columns}
    col_flagged:          dict[str, int]          = {c: 0 for c in text_columns}
    col_high_conf:        dict[str, int]          = {c: 0 for c in text_columns}
    all_scores:   list[float] = []
    flagged_rows: list[dict]  = []

    sem = asyncio.Semaphore(concurrency)

    async def _bounded_score(text: str) -> float:
        async with sem:
            return await _score_text(text)

    for col in text_columns:
        texts_with_idx: list[tuple[int, str]] = []
        for i, row in enumerate(sample):
            val = row.get(col)
            if val and isinstance(val, str) and len(val.strip()) >= 20:
                texts_with_idx.append((i, val.strip()))

        if not texts_with_idx:
            continue

        indices, texts = zip(*texts_with_idx)
        scores = await asyncio.gather(*[_bounded_score(t) for t in texts])

        for row_idx, synthetic_score in zip(indices, scores):
            synthetic_score = round(synthetic_score, 4)
            col_synthetic_scores[col].append(synthetic_score)
            all_scores.append(synthetic_score)

            if synthetic_score >= flag_threshold:
                col_flagged[col] += 1
                if synthetic_score >= high_threshold:
                    col_high_conf[col] += 1
                flagged_rows.append({
                    "row_idx":         row_idx,
                    "column":          col,
                    "synthetic_score": synthetic_score,
                    "confidence":      "high" if synthetic_score >= high_threshold else "medium",
                })

    # ── Per-column summary ─────────────────────────────────────────────────────
    column_summaries = []
    for col in text_columns:
        scores = col_synthetic_scores[col]
        if not scores:
            continue
        n       = len(scores)
        avg     = round(sum(scores) / n, 4)
        flagged = col_flagged[col]
        high    = col_high_conf[col]

        buckets = {"human": 0, "uncertain": 0, "likely_synthetic": 0, "synthetic": 0}
        for s in scores:
            if s < 0.3:
                buckets["human"] += 1
            elif s < flag_threshold:
                buckets["uncertain"] += 1
            elif s < high_threshold:
                buckets["likely_synthetic"] += 1
            else:
                buckets["synthetic"] += 1

        column_summaries.append({
            "column":        col,
            "rows_analyzed": n,
            "avg_score":     avg,
            "flagged":       flagged,
            "flagged_pct":   round(flagged / total_rows * 100, 1),
            "high_conf":     high,
            "distribution":  buckets,
        })

    column_summaries.sort(key=lambda x: x["avg_score"], reverse=True)

    n_all         = len(all_scores) or 1
    avg_all       = round(sum(all_scores) / n_all, 4)
    total_flagged = len(flagged_rows)
    total_high    = sum(col_high_conf.values())

    if avg_all >= high_threshold:
        verdict = "mostly_synthetic"
    elif avg_all >= flag_threshold:
        verdict = "mixed"
    elif avg_all >= 0.3:
        verdict = "likely_human"
    else:
        verdict = "human"

    histogram = [0] * 10
    for s in all_scores:
        bucket = min(int(s * 10), 9)
        histogram[bucket] += 1

    flagged_rows.sort(key=lambda x: x["synthetic_score"], reverse=True)

    return {
        "rows_analyzed":   total_rows,
        "total_flagged":   total_flagged,
        "flagged_pct":     round(total_flagged / total_rows * 100, 1) if total_rows else 0,
        "total_high_conf": total_high,
        "avg_score":       avg_all,
        "verdict":         verdict,
        "histogram":       histogram,
        "column_summaries": column_summaries,
        "flagged_rows":    flagged_rows[:50],
        "flag_threshold":  flag_threshold,
        "high_threshold":  high_threshold,
    }


# ── Request model ──────────────────────────────────────────────────────────────

class SyntheticRequest(BaseModel):
    rows:           list[dict]
    text_columns:   list[str]
    max_rows:       int   = 300
    concurrency:    int   = 20
    flag_threshold: float = FLAG_THRESHOLD
    high_threshold: float = HIGH_THRESHOLD


# ── Route ──────────────────────────────────────────────────────────────────────

@router.post("/synthetic")
async def synthetic_detection(req: SyntheticRequest):
    if not req.rows:
        raise HTTPException(status_code=400, detail="No rows provided")
    return await run_synthetic_analysis(
        req.rows,
        req.text_columns,
        max_rows=req.max_rows,
        concurrency=req.concurrency,
        flag_threshold=req.flag_threshold,
        high_threshold=req.high_threshold,
    )