"""Evidence gates for in-thread task plan step completion."""

from __future__ import annotations

import logging
import re
from typing import Any

_log = logging.getLogger(__name__)

_EVIDENCE_KEYWORDS: tuple[str, ...] = (
    "render",
    "lint",
    "implement",
    "html",
    "video",
    "deploy",
    "write file",
    "create project",
)

_TOOL_SUCCESS_PATTERNS: tuple[tuple[str, re.Pattern[str]], ...] = (
    ("write_text_file", re.compile(r"Successfully wrote|bytes written|File saved", re.I)),
    ("read_file", re.compile(r"--- .+ ---\n", re.I)),
)


def step_requires_evidence(step: dict[str, Any]) -> bool:
    if not isinstance(step, dict):
        return False
    meta = step.get("meta") if isinstance(step.get("meta"), dict) else {}
    if meta.get("role") == "coordinator":
        return False
    sid = str(step.get("id") or "").lower()
    if sid.startswith("coordinator"):
        return False
    blob = " ".join(
        str(step.get(k) or "")
        for k in ("text", "note", "id")
    ).lower()
    return any(kw in blob for kw in _EVIDENCE_KEYWORDS)


def _coordinator_only_step(step: dict[str, Any]) -> bool:
    meta = step.get("meta") if isinstance(step.get("meta"), dict) else {}
    if meta.get("role") == "coordinator":
        return True
    return str(step.get("id") or "").startswith("coordinator")


def evidence_for_step(thread_id: str, step: dict[str, Any]) -> tuple[bool, str]:
    """Scan parent thread transcript for tool-success patterns matching the step."""
    if not thread_id:
        return False, "no thread context for evidence check"
    try:
        from chat_storage import get_messages

        messages = get_messages(thread_id) or []
    except Exception:
        return False, "evidence check unavailable (storage error); try again"

    blob_parts: list[str] = []
    for msg in messages:
        role = msg.get("role")
        content = msg.get("content")
        if isinstance(content, str):
            blob_parts.append(content)
        elif isinstance(content, list):
            for part in content:
                if isinstance(part, dict) and part.get("type") == "text":
                    blob_parts.append(str(part.get("text") or ""))
        if role == "tool":
            blob_parts.append(str(content or ""))
    transcript = "\n".join(blob_parts)
    if not transcript.strip():
        return False, "no tool evidence in thread transcript for this step"

    step_blob = " ".join(
        str(step.get(k) or "") for k in ("text", "note", "id")
    ).lower()

    if re.search(
        r"kanban_cancel_task|Cancelled \d+ tasks \(root \+ descendants\)",
        transcript,
        re.I,
    ):
        return (
            False,
            "step cannot be marked done after cancelling the Kanban board that was executing it",
        )

    for label, pattern in _TOOL_SUCCESS_PATTERNS:
        if label.replace("_", " ") in step_blob or label.split("_")[0] in step_blob:
            if pattern.search(transcript):
                return True, "ok"
    return False, (
        "step marked done without matching tool success evidence in this thread; "
        "complete the work or leave the step in_progress"
    )


def validate_plan_completion(
    thread_id: str,
    plan: dict[str, Any],
    *,
    coordinator_mode: bool = False,
) -> tuple[bool, str]:
    """Return (ok, error_message) before persisting a plan with done steps."""
    steps = plan.get("steps") if isinstance(plan, dict) else None
    if not isinstance(steps, list):
        return True, ""
    for step in steps:
        if not isinstance(step, dict):
            continue
        if (step.get("status") or "").strip().lower() != "done":
            continue
        if coordinator_mode:
            if not _coordinator_only_step(step):
                return (
                    False,
                    "Coordinator mode: in-thread implementation steps cannot be marked done — "
                    "Kanban workers own execution. Mark only coordinator/monitor steps done, "
                    "or wait for the board to finish.",
                )
            try:
                from utils.kanban_coordinator import probe_board_active

                if probe_board_active(thread_id):
                    return (
                        False,
                        "Coordinator mode: cannot mark monitor steps done while the Kanban "
                        "board still has open tasks.",
                    )
            except Exception:
                _log.debug("coordinator board check during plan save failed", exc_info=True)
                return (
                    False,
                    "Coordinator mode: could not verify board state; try again after workers finish.",
                )
            continue
        if step_requires_evidence(step):
            ok, reason = evidence_for_step(thread_id, step)
            if not ok:
                return False, reason
    return True, ""
