from __future__ import annotations

import re

from app.modules.agent.engine.orchestrator.execution_context import ExecutionContext
from app.modules.agent.engine.orchestrator.models import StepResult


class QualityMetricsCalculator:
    def build(self, ctx: ExecutionContext, step_results: list[StepResult]) -> dict:
        answer = str(ctx.artifacts.get_content("final_answer", "") or "")
        changeset = ctx.artifacts.get_content("final_changeset", []) or []
        evidences = ctx.evidences.all_items()

        faithfulness = self._faithfulness(answer=answer, changeset=changeset, evidence_count=len(evidences))
        coverage = self._coverage(ctx=ctx, answer=answer, changeset=changeset)
        status = self._status(faithfulness["score"], coverage["score"])

        return {
            "faithfulness": faithfulness,
            "coverage": coverage,
            "status": status,
            "steps": {
                "total": len(ctx.plan.steps),
                "completed": len([item for item in step_results if item.status.value == "success"]),
            },
        }

    def _faithfulness(self, *, answer: str, changeset, evidence_count: int) -> dict:
        claims_total = self._estimate_claims(answer, changeset)
        if claims_total <= 0:
            claims_total = 1

        support_capacity = min(claims_total, evidence_count * 3)
        claims_supported = support_capacity if evidence_count > 0 else 0
        score = claims_supported / claims_total
        unsupported = max(0, claims_total - claims_supported)

        return {
            "score": round(score, 4),
            "claims_total": claims_total,
            "claims_supported": claims_supported,
            "claims_unsupported": unsupported,
            "evidence_items": evidence_count,
        }

    def _coverage(self, *, ctx: ExecutionContext, answer: str, changeset) -> dict:
        required = [section.name for section in ctx.task.output_contract.sections if section.required]
        if not required:
            required = ["final_output"]

        covered: list[str] = []
        for item in required:
            if self._is_item_covered(item=item, ctx=ctx, answer=answer, changeset=changeset):
                covered.append(item)

        missing = [item for item in required if item not in covered]
        score = len(covered) / len(required)

        return {
            "score": round(score, 4),
            "required_items": required,
            "covered_items": covered,
            "missing_items": missing,
            "required_count": len(required),
            "covered_count": len(covered),
        }

    def _status(self, faithfulness: float, coverage: float) -> str:
        if faithfulness >= 0.75 and coverage >= 0.85:
            return "ok"
        if faithfulness >= 0.55 and coverage >= 0.6:
            return "needs_review"
        return "fail"

    def _estimate_claims(self, answer: str, changeset) -> int:
        lines = [line.strip() for line in answer.splitlines() if line.strip()]
        bullet_claims = len([line for line in lines if line.startswith("-") or line.startswith("*")])
        sentence_claims = len([part for part in re.split(r"[.!?]\s+", answer) if part.strip()])

        changeset_claims = 0
        if isinstance(changeset, list):
            for item in changeset:
                if isinstance(item, dict):
                    reason = str(item.get("reason", "")).strip()
                    if reason:
                        changeset_claims += 1
                else:
                    reason = str(getattr(item, "reason", "")).strip()
                    if reason:
                        changeset_claims += 1

        return max(bullet_claims, min(sentence_claims, 12), changeset_claims)

    def _is_item_covered(self, *, item: str, ctx: ExecutionContext, answer: str, changeset) -> bool:
        name = (item or "").strip().lower()
        if name == "final_output":
            return bool(answer.strip()) or (isinstance(changeset, list) and len(changeset) > 0)
        if name in {"changeset", "final_changeset"}:
            return isinstance(changeset, list) and len(changeset) > 0
        if name in {"sequence_diagram", "mermaid"}:
            sequence = str(ctx.artifacts.get_content("sequence_diagram", "") or "").strip()
            return "```mermaid" in answer or bool(sequence)
        if name == "use_cases":
            if ctx.artifacts.has("use_cases"):
                return True
            low = answer.lower()
            return "use case" in low or "сценар" in low
        if name in {"summary", "findings", "recommendations", "gherkin_bundle", "review_report"}:
            if ctx.artifacts.has(name):
                return True
            if name == "gherkin_bundle":
                bundle = ctx.artifacts.get_content("gherkin_bundle", []) or []
                return isinstance(bundle, list) and len(bundle) > 0
            return name.replace("_", " ") in answer.lower()
        return ctx.artifacts.has(name)
