from __future__ import annotations

import hashlib
import json

import pytest

from recoil.pipeline._lib import story_gate as sg
from recoil.pipeline.core.take import Beat


ARTIFACT = "prep/ep_001/storyboards/EP001_CONT_004_v03.png"
SOURCE_SHA256 = "a" * 64


def _verdict(route: str = "ok", *, failed: bool = False) -> dict:
    forced_check = {
        "passed": not failed,
        "severity": "HARD" if failed else "SOFT",
        "confidence": 0.91,
        "reason": "Target is behind the actor." if failed else "Visible.",
    }
    return {
        "schema_version": sg.SCHEMA_VERSION,
        "judge_model": "fixture-model",
        "prompt_version": sg.PROMPT_VERSION,
        "board_id": "EP001_CONT_004_v03",
        "source_sha256": SOURCE_SHA256,
        "text_stageability": None,
        "panels": [
            {
                "index": 1,
                "description": "Jade faces away from the pod.",
                "forced_checks": {"spatially_possible": forced_check},
                "fix_hint_injectable": False,
            }
        ],
        "transitions": [],
        "routing": {
            "class": route,
            "confidence": 0.82,
            "evidence": "fixture evidence",
        },
    }


def _summary(route: str = "ok") -> dict:
    return {
        "mode": "shadow",
        "route": route,
        "severity": "ok",
        "confidence": 0.82,
        "summary": "ok",
        "judge_model": "fixture-model",
        "prompt_version": sg.PROMPT_VERSION,
        "verdict_path": "prep/ep_001/storyboards/EP001_CONT_004_v03.verdict.json",
        "verdict_hash": "b" * 64,
    }


def test_write_verdict_creates_hashes_round_trips_and_leaves_no_tmp(tmp_path):
    path, verdict_hash = sg.write_verdict(
        tmp_path,
        "EP001_CONT_004_v03",
        _verdict(),
    )

    assert path == tmp_path / "EP001_CONT_004_v03.verdict.json"
    assert path.exists()
    body = path.read_bytes()
    assert hashlib.sha256(body).hexdigest() == verdict_hash
    assert json.loads(body) == _verdict()
    assert b'\n  "schema_version"' in body
    assert list(tmp_path.glob("*.tmp")) == []


def test_write_verdict_rewrite_overwrites_and_changes_hash(tmp_path):
    path, first_hash = sg.write_verdict(
        tmp_path,
        "EP001_CONT_004_v03",
        _verdict(),
    )
    second = _verdict("board_problem", failed=True)
    second_path, second_hash = sg.write_verdict(
        tmp_path,
        "EP001_CONT_004_v03",
        second,
    )

    assert second_path == path
    assert second_hash != first_hash
    assert json.loads(path.read_text(encoding="utf-8")) == second
    assert list(tmp_path.glob("*.tmp")) == []


def test_verdict_summary_derives_projection_fields():
    summary = sg.verdict_summary(
        _verdict("board_problem", failed=True),
        mode="shadow",
        verdict_path="prep/ep_001/storyboards/EP001_CONT_004_v03.verdict.json",
        verdict_hash="c" * 64,
    )

    assert summary["mode"] == "shadow"
    assert summary["route"] == "board_problem"
    assert summary["severity"] == "HARD"
    assert summary["confidence"] == 0.82
    assert summary["summary"] == "Target is behind the actor."
    assert summary["judge_model"] == "fixture-model"
    assert summary["prompt_version"] == sg.PROMPT_VERSION
    assert summary["verdict_hash"] == "c" * 64


def test_set_board_story_gate_attaches_projection_with_updated_at():
    beat = Beat("EP001_CONT_004")
    beat.set_board_proposed(ARTIFACT, SOURCE_SHA256)

    beat.set_board_story_gate(_summary("ok"))

    assert beat.board["story_gate"]["route"] == "ok"
    assert beat.board["story_gate"]["verdict_hash"] == "b" * 64
    assert isinstance(beat.board["story_gate"]["updated_at"], str)


def test_set_board_story_gate_raises_without_board():
    beat = Beat("EP001_CONT_004")

    with pytest.raises(ValueError, match="board must exist"):
        beat.set_board_story_gate(_summary("ok"))


def test_set_board_story_gate_raises_on_bad_route():
    beat = Beat("EP001_CONT_004")
    beat.set_board_proposed(ARTIFACT, SOURCE_SHA256)

    with pytest.raises(ValueError, match="story gate route"):
        beat.set_board_story_gate(_summary("bad_route"))


def test_approve_after_story_gate_attach_preserves_projection():
    beat = Beat("EP001_CONT_004")
    beat.set_board_proposed(ARTIFACT, SOURCE_SHA256)
    beat.set_board_story_gate(_summary("ok"))

    beat.approve_board("JT")

    assert beat.board["status"] == "approved"
    assert beat.board["approved_by"] == "JT"
    assert beat.board["story_gate"]["route"] == "ok"
    assert beat.board["story_gate"]["verdict_hash"] == "b" * 64
