from __future__ import annotations

from app.modules.agent.engine.orchestrator.execution_context import ExecutionContext
from app.modules.agent.engine.orchestrator.models import PlanStep, QualityGateRef


class QualityGateRunner:
    def check_step(self, step: PlanStep, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        return self._run(step.quality_gates, step=step, ctx=ctx)

    def check_global(self, gates: list[QualityGateRef], ctx: ExecutionContext) -> tuple[bool, list[str]]:
        return self._run(gates, step=None, ctx=ctx)

    def _run(self, gates: list[QualityGateRef], *, step: PlanStep | None, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        failures: list[str] = []
        warnings: list[str] = []
        for gate in gates:
            ok, details = self._check(gate.gate_id, step=step, ctx=ctx)
            if ok:
                continue
            if gate.blocking:
                failures.extend(details)
            else:
                warnings.extend(details)
        return len(failures) == 0, failures + warnings

    def _check(self, gate_id: str, *, step: PlanStep | None, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        checks = {
            "required_outputs": lambda: self._required_outputs(step, ctx),
            "non_empty_answer_or_changeset": lambda: self._non_empty_output(ctx),
            "changeset_required_for_write": lambda: self._changeset_required(ctx),
            "changeset_schema": lambda: self._changeset_schema(ctx),
            "evidence_required": lambda: self._evidence_required(ctx),
            "review_report_schema": lambda: self._review_schema(ctx),
            "cross_file_consistency": lambda: self._cross_file_consistency(ctx),
            "target_path_must_exist_or_be_allowed": lambda: self._target_path_gate(ctx),
            "minimal_patch_policy": lambda: self._minimal_patch_policy(ctx),
            "gherkin_syntax_lint": lambda: self._gherkin_lint(ctx),
            "coverage_of_change_intents": lambda: self._coverage_gate(ctx),
            "explain_format_hint": lambda: self._explain_hint(ctx),
        }
        fn = checks.get(gate_id)
        if fn is None:
            return True, []
        return fn()

    def _required_outputs(self, step: PlanStep | None, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        if step is None:
            return True, []
        missing = [f"missing_required_artifact:{spec.key}" for spec in step.outputs if spec.required and not ctx.artifacts.has(spec.key)]
        return len(missing) == 0, missing

    def _non_empty_output(self, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        answer = str(ctx.artifacts.get_content("final_answer", "") or "").strip()
        changeset = ctx.artifacts.get_content("final_changeset", []) or []
        ok = bool(answer) or (isinstance(changeset, list) and len(changeset) > 0)
        return ok, [] if ok else ["empty_final_output"]

    def _changeset_required(self, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        if not ctx.task.constraints.allow_writes:
            return True, []
        changeset = ctx.artifacts.get_content("final_changeset", []) or []
        ok = isinstance(changeset, list) and len(changeset) > 0
        return ok, [] if ok else ["changeset_required_for_write"]

    def _changeset_schema(self, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        changeset = ctx.artifacts.get_content("final_changeset", []) or []
        if not isinstance(changeset, list):
            return False, ["changeset_not_list"]
        for idx, item in enumerate(changeset):
            if not isinstance(item, dict):
                return False, [f"changeset_item_not_object:{idx}"]
            if not item.get("op") or not item.get("path"):
                return False, [f"changeset_item_missing_fields:{idx}"]
        return True, []

    def _evidence_required(self, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        if not ctx.task.source_policy.require_evidence:
            return True, []
        evidences = ctx.evidences.all_items()
        return len(evidences) > 0, ([] if evidences else ["no_evidence_collected"])

    def _review_schema(self, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        report = str(ctx.artifacts.get_content("review_report", "") or "")
        ok = "## Findings" in report and "## Recommendations" in report
        return ok, [] if ok else ["review_report_missing_sections"]

    def _cross_file_consistency(self, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        report = ctx.artifacts.get_content("consistency_report", {}) or {}
        ok = bool(report.get("required_core_paths_present"))
        return ok, [] if ok else ["cross_file_consistency_failed"]

    def _target_path_gate(self, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        target = ctx.artifacts.get_content("resolved_target", {}) or {}
        ok = bool(str(target.get("path", "")).strip())
        return ok, [] if ok else ["target_path_not_resolved"]

    def _minimal_patch_policy(self, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        report = ctx.artifacts.get_content("patch_validation_report", {}) or {}
        ok = bool(report.get("safe"))
        return ok, [] if ok else ["patch_validation_failed"]

    def _gherkin_lint(self, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        report = ctx.artifacts.get_content("gherkin_lint_report", {}) or {}
        ok = bool(report.get("valid"))
        return ok, [] if ok else ["gherkin_lint_failed"]

    def _coverage_gate(self, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        report = ctx.artifacts.get_content("coverage_report", {}) or {}
        ok = bool(report.get("covered"))
        return ok, [] if ok else ["coverage_check_failed"]

    def _explain_hint(self, ctx: ExecutionContext) -> tuple[bool, list[str]]:
        answer = str(ctx.artifacts.get_content("final_answer", "") or "")
        ok = "```mermaid" in answer or "sequenceDiagram" in answer
        return ok, [] if ok else ["hint:explain_answer_missing_mermaid_block"]
