# retoor <retoor@molodetz.nl>
from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import Optional

from devplacepy.services.jobs.isslop.agent.llm import LlmClient, LlmUnavailableError, extract_json_object
from devplacepy.services.jobs.isslop.analysis.scoring import (
    CATEGORY_HUMAN_CLEAN,
    CATEGORY_HUMAN_MESSY,
    CATEGORY_SLOP,
    CATEGORY_SOPHISTICATED,
    CATEGORY_UNCERTAIN,
    FileScore,
)
from devplacepy.services.jobs.isslop.analysis.signals import SEVERITY_STRONG
from devplacepy.services.jobs.isslop.config import AI_EXCERPT_CHARS, AI_SAMPLE_LIMIT

logger = logging.getLogger(__name__)

VALID_CATEGORIES: frozenset[str] = frozenset(
    {CATEGORY_SLOP, CATEGORY_SOPHISTICATED, CATEGORY_HUMAN_CLEAN, CATEGORY_HUMAN_MESSY, CATEGORY_UNCERTAIN}
)

SYSTEM_PROMPT: str = (
    "You are a deterministic source-code provenance and quality auditor. "
    "You classify code on two independent axes: origin (was it AI-generated) and "
    "curation quality (was it reviewed, integrated and production-worthy). "
    "The categories are: ai-slop (AI origin, low curation: the model's defaults shipped as-is), "
    "sophisticated-ai (AI origin, high curation: the model typed, the engineer decided), "
    "human-clean and human-messy (code no model would write that way), uncertain. "
    "Human messiness is not slop. Clean AI-assisted code is not slop. "
    "Judge only from the evidence given. Respond with a single JSON object and nothing else, using exactly these keys: "
    '{"origin_score": <0-100 integer, likelihood of AI generation>, '
    '"quality_deficit": <0-100 integer, density of anti-patterns and missing curation>, '
    '"ai_probability": <0-100 integer>, '
    '"category": <one of ai-slop|sophisticated-ai|human-clean|human-messy|uncertain>, '
    '"reasoning": <two to four factual sentences citing concrete evidence>, '
    '"notable_signals": [<up to five short strings>]}'
)


@dataclass(frozen=True)
class AiVerdict:
    path: str
    origin_score: float
    quality_deficit: float
    ai_probability: float
    category: str
    reasoning: str
    notable_signals: list[str]


def select_samples(scores: list[FileScore], limit: int = AI_SAMPLE_LIMIT) -> list[FileScore]:
    def interest(entry: FileScore) -> tuple[float, str]:
        strong = sum(1 for signal in entry.signals if signal.severity == SEVERITY_STRONG)
        return (-(entry.sloc * (1.0 + strong) * entry.criticality), entry.relative)

    ranked = sorted((entry for entry in scores if entry.sloc >= 10), key=interest)
    return ranked[:limit]


def _clamp(value: object, fallback: float) -> float:
    if isinstance(value, (int, float)) and not isinstance(value, bool):
        return float(min(100.0, max(0.0, value)))
    return fallback


async def classify_file(
    llm: LlmClient,
    score: FileScore,
    excerpt: str,
) -> Optional[AiVerdict]:
    signal_lines = "\n".join(
        f"- [{signal.severity}] {signal.code} line {signal.line}: {signal.title}"
        for signal in score.signals[:15]
    ) or "- none detected"
    user = (
        f"File: {score.relative}\n"
        f"Language: {score.language}\n"
        f"Static origin score: {score.origin_score}\n"
        f"Static quality deficit: {score.quality_deficit}\n"
        f"Static signals:\n{signal_lines}\n\n"
        f"Source excerpt (may be truncated):\n```\n{excerpt[:AI_EXCERPT_CHARS]}\n```"
    )
    try:
        answer = await llm.complete(SYSTEM_PROMPT, user)
    except LlmUnavailableError as error:
        logger.warning("AI classification unavailable for %s: %s", score.relative, error)
        return None
    parsed = extract_json_object(answer)
    if parsed is None:
        logger.warning("AI classification returned unparseable output for %s", score.relative)
        return None
    category = str(parsed.get("category", CATEGORY_UNCERTAIN)).strip().lower()
    if category not in VALID_CATEGORIES:
        category = CATEGORY_UNCERTAIN
    signals_field = parsed.get("notable_signals")
    notable = [str(item)[:120] for item in signals_field[:5]] if isinstance(signals_field, list) else []
    verdict = AiVerdict(
        path=score.relative,
        origin_score=_clamp(parsed.get("origin_score"), score.origin_score),
        quality_deficit=_clamp(parsed.get("quality_deficit"), score.quality_deficit),
        ai_probability=_clamp(parsed.get("ai_probability"), score.origin_score),
        category=category,
        reasoning=str(parsed.get("reasoning", ""))[:1200],
        notable_signals=notable,
    )
    logger.info("AI verdict for %s: %s (ai %d%%)", verdict.path, verdict.category, int(verdict.ai_probability))
    return verdict
