"""The conversation view of a task.

The state machine stays exactly as the design requires: stages advance only on validated
JSON artifacts, and no agent ever reads another's raw output. This module is the surface
on top of that, turning each validated artifact into something a person can read and reply
to, and carrying the operator's replies forward as constraints.

Two kinds of text meet here and they are not equal. An operator message is an instruction
from the principal using the tool. An agent message is a rendering of data that agent
produced. Only the first is ever presented to a provider as something to obey.
"""

from __future__ import annotations

import json
from datetime import datetime, timezone
from pathlib import Path
from typing import Literal

from pydantic import Field

from .domain import (
    FixArtifact,
    ImplementationArtifact,
    PlanArtifact,
    ReviewArtifact,
    StageTestReport,
    VersionedArtifact,
)

Speaker = Literal["operator", "planner", "implementer", "orchestrator"]


class Turn(VersionedArtifact):
    speaker: Speaker
    text: str
    stage: str = ""
    #: Artifact file this turn renders, when it renders one.
    artifact: str | None = None
    at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))

    #: True once the orchestrator has carried an operator turn into a provider prompt.
    delivered: bool = False


class Conversation:
    """Append-only transcript beside the task's other artifacts."""

    def __init__(self, task_dir: str | Path) -> None:
        self.path = Path(task_dir) / "conversation.jsonl"

    def all(self) -> list[Turn]:
        if not self.path.is_file():
            return []
        turns = []
        for line in self.path.read_text(encoding="utf-8").splitlines():
            if line.strip():
                try:
                    turns.append(Turn.model_validate_json(line))
                except ValueError:
                    continue  # one bad line must not hide the transcript
        return turns

    def add(self, speaker: Speaker, text: str, stage: str = "", artifact: str | None = None) -> Turn:
        turn = Turn(speaker=speaker, text=text.strip(), stage=stage, artifact=artifact)
        self.path.parent.mkdir(parents=True, exist_ok=True)
        with self.path.open("a", encoding="utf-8", newline="\n") as stream:
            stream.write(turn.model_dump_json() + "\n")
        return turn

    def pending_operator_text(self) -> list[str]:
        """Operator messages not yet carried into a prompt."""
        return [turn.text for turn in self.all() if turn.speaker == "operator" and not turn.delivered]

    def mark_delivered(self) -> None:
        """Rewrite the transcript with every operator turn marked as carried."""
        turns = self.all()
        if not any(turn.speaker == "operator" and not turn.delivered for turn in turns):
            return
        updated = [
            turn.model_copy(update={"delivered": True}) if turn.speaker == "operator" else turn
            for turn in turns
        ]
        temporary = self.path.with_suffix(".jsonl.tmp")
        temporary.write_text(
            "".join(turn.model_dump_json() + "\n" for turn in updated), encoding="utf-8", newline="\n"
        )
        temporary.replace(self.path)

    def guidance(self) -> str:
        """Undelivered operator messages, phrased for a prompt.

        This is the one block in a prompt that an agent is told to obey, because it comes
        from the person running the tool rather than from a repository or another agent.
        """
        pending = self.pending_operator_text()
        if not pending:
            return ""
        lines = ["OPERATOR GUIDANCE (from the person running this task; follow it):"]
        lines += [f"- {text}" for text in pending]
        return "\n".join(lines)


def render(artifact, stage: str) -> str:
    """Turn a validated artifact into the sentence a person would have written."""
    if isinstance(artifact, PlanArtifact):
        lines = [f"计划：{artifact.goal}"]
        if artifact.steps:
            lines += [f"{index}. {step.action}" for index, step in enumerate(artifact.steps, 1)]
        if artifact.acceptance_criteria:
            lines.append("验收标准：" + "；".join(artifact.acceptance_criteria))
        if artifact.risks:
            lines.append("风险：" + "；".join(risk.risk for risk in artifact.risks))
        return "\n".join(lines)

    if isinstance(artifact, ReviewArtifact):
        verdict = "通过" if artifact.verdict == "PASS" else "要求修改"
        lines = [f"{_stage_label(stage)}结论：{verdict}"]
        if artifact.summary:
            lines.append(artifact.summary)
        lines += [f"[{issue.severity}] {issue.issue_id} {issue.problem}" for issue in artifact.issues]
        return "\n".join(lines)

    if isinstance(artifact, ImplementationArtifact):
        lines = ["实现完成。"]
        if artifact.files_changed:
            lines.append("改动文件：" + "、".join(artifact.files_changed))
        if artifact.notes:
            lines.append(artifact.notes)
        return "\n".join(lines)

    if isinstance(artifact, FixArtifact):
        resolved = "、".join(f"{item.issue_id}（{item.status}）" for item in artifact.resolved)
        lines = ["修复完成。"]
        if resolved:
            lines.append("已处理：" + resolved)
        if artifact.not_resolved:
            lines.append("未处理：" + "、".join(item.issue_id for item in artifact.not_resolved))
        return "\n".join(lines)

    if isinstance(artifact, StageTestReport):
        outcome = "通过" if artifact.passed else "失败"
        detail = artifact.summary or f"退出码 {artifact.exit_code}"
        return f"测试{outcome}：{detail}"

    return json.dumps(getattr(artifact, "model_dump", lambda **_: {})(mode="json"), ensure_ascii=False)


def _stage_label(stage: str) -> str:
    return {"PLAN_REVIEW": "计划复核", "REVIEW": "审查", "FINAL_VERIFY": "终审"}.get(stage, stage)
