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

import json
import logging
from typing import Any

from devplacepy.services.jobs.isslop.agent.classifier import AiVerdict
from devplacepy.services.jobs.isslop.agent.llm import LlmClient, LlmUnavailableError
from devplacepy.services.jobs.isslop.agent.vision import ImageVerdict
from devplacepy.services.jobs.isslop.analysis.domsignals.aggregate import DomEvidence
from devplacepy.services.jobs.isslop.analysis.scoring import FileScore, RepoScores
from devplacepy.services.jobs.isslop.analysis.signals import SEVERITY_STRONG
from devplacepy.services.jobs.isslop.analysis.templates import TemplateEvidence

logger = logging.getLogger(__name__)

REPORT_SYSTEM_PROMPT: str = (
    "You are a technical auditor writing the final report of a source-code AI usage classification. "
    "Write factual, professional markdown. No emoji, no marketing language, no hedging filler. "
    "Structure: '# AI Usage Classification Report' title, then sections 'Verdict', 'Scores', "
    "'Evidence', 'Notable Files', 'Image Review' (only if image data is present), 'Methodology Notes', 'Caveats'. "
    "Cite concrete files and signals from the data, and wrap EVERY file path you mention in backticks; "
    "when citing a specific line, write it as `path:line` (like `src/libs/Env.ts:12`) so the platform can "
    "link the reference to the exact annotated source line. "
    "Present the result as calibrated guidance, not accusation. "
    "Category definitions: ai-slop ships the model's defaults untouched; sophisticated-ai shows AI steered by "
    "an engineer who knows what they are doing; human is code no model would write that way. "
    "When template_provenance markers are present, explain that the project ships a starter template's "
    "defaults and name the template evidence; shipping scaffold defaults counts against authenticity. "
    "A near-certain unmodified starter template IS ai-slop by definition: the defaults were shipped as-is, "
    "and clean scaffold code does not change that - the curation belongs to the template author, not the "
    "presenter. Never call an untouched template sophisticated-ai. "
    "When dom_evidence.detected_builder is present, lead the Verdict section with a line stating the site was "
    "detected as built with that no-code builder, treating the rendered-page fingerprint match as near-certain "
    "evidence. "
    "Keep it under 900 words. Output only markdown."
)


def _summary_payload(
    source_url: str,
    source_kind: str,
    scores: RepoScores,
    files: list[FileScore],
    verdicts: list[AiVerdict],
    excluded_count: int,
    image_verdicts: list[ImageVerdict],
    image_stats: dict[str, Any],
    template: TemplateEvidence,
    dom_evidence: DomEvidence | None,
) -> dict[str, Any]:
    worst = sorted(files, key=lambda entry: (-entry.quality_deficit, entry.relative))[:10]
    strongest = [
        {
            "path": entry.relative,
            "origin": entry.origin_score,
            "quality_deficit": entry.quality_deficit,
            "category": entry.category,
            "signals": [signal.to_dict() for signal in entry.signals if signal.severity == SEVERITY_STRONG][:4],
        }
        for entry in worst
    ]
    return {
        "source_url": source_url,
        "source_kind": source_kind,
        "repo_scores": {
            "grade": scores.grade,
            "slop_score": scores.slop_score,
            "origin_score": scores.origin_score,
            "quality_deficit": scores.quality_deficit,
            "category": scores.category,
            "human_percent": scores.human_percent,
            "ai_percent": scores.ai_percent,
            "confidence": scores.confidence,
            "files_scored": scores.files_scored,
            "excluded_files": excluded_count,
            "strong_signals": scores.strong_signal_count,
            "medium_signals": scores.medium_signal_count,
        },
        "worst_files": strongest,
        "ai_verdicts": [
            {
                "path": verdict.path,
                "category": verdict.category,
                "ai_probability": verdict.ai_probability,
                "reasoning": verdict.reasoning,
                "notable_signals": verdict.notable_signals,
            }
            for verdict in verdicts
        ],
        "template_provenance": {
            "score": template.score,
            "markers": template.markers,
        },
        "image_review": image_stats,
        "image_verdicts": [
            {
                "path": verdict.relative,
                "grade": verdict.grade,
                "verdict": verdict.verdict,
                "ai_probability": verdict.ai_probability,
                "image_kind": verdict.image_kind,
                "tells": verdict.tells,
                "description": verdict.description,
            }
            for verdict in sorted(image_verdicts, key=lambda item: -item.ai_probability)[:12]
        ],
        "dom_evidence": (
            {
                "detected_builder": dom_evidence.detected_builder,
                "builder_confidence": dom_evidence.builder_confidence,
                "dom_slop_score": dom_evidence.score,
                "bucket": dom_evidence.bucket,
                "top_signals": [signal.to_dict() for signal in dom_evidence.signals[:6]],
            }
            if dom_evidence is not None
            else {"detected_builder": None, "builder_confidence": 0.0, "dom_slop_score": 0.0, "bucket": "none", "top_signals": []}
        ),
    }


def fallback_report(payload: dict[str, Any]) -> str:
    repo = payload["repo_scores"]
    lines: list[str] = [
        "# AI Usage Classification Report",
        "",
        f"Source: `{payload['source_url']}` ({payload['source_kind']})",
        "",
        "## Verdict",
        "",
        f"Category **{repo['category']}** with grade **{repo['grade']}** "
        f"(slop score {repo['slop_score']}/100, confidence {repo['confidence']}).",
    ]
    dom_evidence = payload.get("dom_evidence") or {}
    if dom_evidence.get("detected_builder"):
        lines.append(
            f"Detected: built with {dom_evidence['detected_builder']} (rendered-page fingerprint, "
            f"confidence {dom_evidence.get('builder_confidence', 0.0):.0%})."
        )
    lines.extend([
        f"Estimated composition: **{repo['human_percent']}% human**, **{repo['ai_percent']}% AI**.",
        "",
        "## Scores",
        "",
        "| Metric | Value |",
        "| --- | --- |",
        f"| Grade | {repo['grade']} |",
        f"| Slop score | {repo['slop_score']} / 100 |",
        f"| Origin score | {repo['origin_score']} / 100 |",
        f"| Quality deficit | {repo['quality_deficit']} / 100 |",
        f"| Human share | {repo['human_percent']}% |",
        f"| AI share | {repo['ai_percent']}% |",
        f"| Files scored | {repo['files_scored']} |",
        f"| Files excluded | {repo['excluded_files']} |",
        f"| Strong signals | {repo['strong_signals']} |",
        f"| Medium signals | {repo['medium_signals']} |",
        "",
        "## Notable Files",
        "",
    ])
    for entry in payload["worst_files"]:
        lines.append(
            f"- `{entry['path']}`: {entry['category']}, origin {entry['origin']}, quality deficit {entry['quality_deficit']}"
        )
        for signal in entry["signals"]:
            lines.append(f"  - [{signal['severity']}] {signal['title']} (line {signal['line']})")
    template = payload.get("template_provenance", {})
    if template.get("markers"):
        lines.extend(["", "## Template Provenance", ""])
        lines.append(
            f"Starter template evidence score **{template.get('score')}/100**. The authenticity grade is "
            "reduced accordingly: an unmodified starter ships its scaffold's defaults rather than original work."
        )
        for marker in template["markers"]:
            lines.append(f"- {marker}")
    if payload["ai_verdicts"]:
        lines.extend(["", "## AI Review Findings", ""])
        for verdict in payload["ai_verdicts"]:
            lines.append(f"- `{verdict['path']}`: {verdict['category']} ({verdict['ai_probability']}% AI). {verdict['reasoning']}")
    image_review = payload.get("image_review", {})
    if payload.get("image_verdicts"):
        lines.extend(
            [
                "",
                "## Image Review",
                "",
                f"Reviewed {image_review.get('count', 0)} images; "
                f"{image_review.get('ai_generated_count', 0)} appear AI-generated "
                f"(mean AI likelihood {image_review.get('mean_ai_probability', 0)}%, image grade {image_review.get('grade', 'n/a')}).",
                "",
            ]
        )
        for verdict in payload["image_verdicts"]:
            tells = f" Tells: {', '.join(verdict['tells'])}." if verdict["tells"] else ""
            lines.append(
                f"- `{verdict['path']}` ({verdict['image_kind']}): grade {verdict['grade']}, {verdict['verdict']} "
                f"({verdict['ai_probability']}% AI). {verdict['description']}{tells}"
            )
    lines.extend(
        [
            "",
            "## Caveats",
            "",
            "No detector is definitive. This report combines weighted static signals with an AI review pass "
            "and should be read as calibrated guidance rather than proof of provenance.",
        ]
    )
    return "\n".join(lines)


async def generate_report(
    llm: LlmClient,
    source_url: str,
    source_kind: str,
    scores: RepoScores,
    files: list[FileScore],
    verdicts: list[AiVerdict],
    excluded_count: int,
    image_verdicts: list[ImageVerdict],
    image_stats: dict[str, Any],
    template: TemplateEvidence,
    dom_evidence: DomEvidence | None,
) -> tuple[str, str]:
    payload = _summary_payload(
        source_url,
        source_kind,
        scores,
        files,
        verdicts,
        excluded_count,
        image_verdicts,
        image_stats,
        template,
        dom_evidence,
    )
    user = "Write the final report for this classification data:\n" + json.dumps(payload, indent=2)[:24000]
    try:
        markdown = await llm.complete(REPORT_SYSTEM_PROMPT, user)
        if "# " not in markdown:
            raise LlmUnavailableError("report missing markdown structure")
        logger.info("Report generated via %s (%d chars)", llm.active_backend_name, len(markdown))
        return markdown.strip(), llm.active_backend_name
    except LlmUnavailableError as error:
        logger.warning("LLM report generation failed, using deterministic fallback: %s", error)
        return fallback_report(payload), "static-fallback"
