"""Tests for lib/screen_test.py — Screen Test state models and persistence."""

import json
from pathlib import Path

import pytest

from recoil.core.paths import STATE_NAMESPACE
from recoil.pipeline._lib.screen_test import (
    GenerationRecord,
    PhaseState,
    CharacterScreenTest,
    ScreenTestState,
    load_screen_test_state,
    save_screen_test_state,
    record_generation,
    apply_verdict,
)


# ── Default Model Construction ───────────────────────────────────────


class TestDefaultConstruction:
    def test_generation_record_defaults(self):
        rec = GenerationRecord(image_path="/tmp/img.png", prompt="test prompt")
        assert rec.image_path == "/tmp/img.png"
        assert rec.prompt == "test prompt"
        assert rec.note is None
        assert rec.verdict is None
        assert rec.timestamp is not None

    def test_phase_state_defaults(self):
        ps = PhaseState(phase_id="jinx_salvager")
        assert ps.phase_id == "jinx_salvager"
        assert ps.status == "empty"
        assert ps.locked_image is None
        assert ps.director_note is None
        assert ps.enriched_prompt is None
        assert ps.held_images == []
        assert ps.generation_history == []

    def test_character_screen_test_defaults(self):
        cst = CharacterScreenTest()
        assert cst.anchor_phase is None
        assert cst.phases == {}

    def test_screen_test_state_defaults(self):
        state = ScreenTestState()
        assert state.characters == {}


# ── Save / Load Roundtrip ────────────────────────────────────────────


class TestSaveLoadRoundtrip:
    def test_save_and_load_empty_state(self, tmp_path):
        state = ScreenTestState()
        save_screen_test_state(tmp_path, state)
        loaded = load_screen_test_state(tmp_path)
        assert loaded.characters == {}

    def test_save_and_load_populated_state(self, tmp_path):
        phase = PhaseState(phase_id="jinx_salvager", status="locked")
        phase.locked_image = "/tmp/locked.png"
        phase.held_images = ["/tmp/held1.png", "/tmp/held2.png"]
        phase.generation_history = [
            GenerationRecord(image_path="/tmp/gen1.png", prompt="prompt 1"),
            GenerationRecord(image_path="/tmp/gen2.png", prompt="prompt 2", note="good take"),
        ]

        char_test = CharacterScreenTest(
            anchor_phase="jinx_salvager",
            phases={"jinx_salvager": phase},
        )
        state = ScreenTestState(characters={"JINX": char_test})

        save_screen_test_state(tmp_path, state)
        loaded = load_screen_test_state(tmp_path)

        assert "JINX" in loaded.characters
        loaded_phase = loaded.characters["JINX"].phases["jinx_salvager"]
        assert loaded_phase.status == "locked"
        assert loaded_phase.locked_image == "/tmp/locked.png"
        assert len(loaded_phase.held_images) == 2
        assert len(loaded_phase.generation_history) == 2
        assert loaded_phase.generation_history[1].note == "good take"

    def test_save_creates_directories(self, tmp_path):
        nested = tmp_path / "deep" / "nested"
        state = ScreenTestState()
        save_screen_test_state(nested, state)
        assert (nested / "state" / STATE_NAMESPACE / "screen_test_state.json").exists()

    def test_saved_file_is_valid_json(self, tmp_path):
        state = ScreenTestState(characters={
            "AVA": CharacterScreenTest(anchor_phase="ava_officer"),
        })
        save_screen_test_state(tmp_path, state)
        json_path = tmp_path / "state" / STATE_NAMESPACE / "screen_test_state.json"
        data = json.loads(json_path.read_text())
        assert "characters" in data
        assert "AVA" in data["characters"]


# ── Load Missing File ────────────────────────────────────────────────


class TestLoadMissingFile:
    def test_load_missing_file_returns_empty(self, tmp_path):
        state = load_screen_test_state(tmp_path)
        assert isinstance(state, ScreenTestState)
        assert state.characters == {}

    def test_load_missing_nested_dir_returns_empty(self, tmp_path):
        state = load_screen_test_state(tmp_path / "nonexistent" / "path")
        assert isinstance(state, ScreenTestState)
        assert state.characters == {}


# ── record_generation ─────────────────────────────────────────────────


class TestRecordGeneration:
    def test_appends_to_history(self):
        phase = PhaseState(phase_id="jinx_salvager")
        record_generation(phase, "/tmp/img1.png", "prompt 1")
        assert len(phase.generation_history) == 1
        assert phase.generation_history[0].image_path == "/tmp/img1.png"
        assert phase.generation_history[0].prompt == "prompt 1"

    def test_updates_status_to_generated(self):
        phase = PhaseState(phase_id="jinx_salvager")
        assert phase.status == "empty"
        record_generation(phase, "/tmp/img1.png", "prompt 1")
        assert phase.status == "generated"

    def test_multiple_generations_append(self):
        phase = PhaseState(phase_id="jinx_salvager")
        record_generation(phase, "/tmp/img1.png", "prompt 1")
        record_generation(phase, "/tmp/img2.png", "prompt 2", note="better")
        assert len(phase.generation_history) == 2
        assert phase.generation_history[1].note == "better"
        assert phase.status == "generated"

    def test_note_is_optional(self):
        phase = PhaseState(phase_id="jinx_salvager")
        record_generation(phase, "/tmp/img.png", "prompt")
        assert phase.generation_history[0].note is None

    def test_note_is_stored(self):
        phase = PhaseState(phase_id="jinx_salvager")
        record_generation(phase, "/tmp/img.png", "prompt", note="director note here")
        assert phase.generation_history[0].note == "director note here"


# ── apply_verdict ─────────────────────────────────────────────────────


class TestApplyVerdict:
    def _phase_with_generation(self):
        phase = PhaseState(phase_id="jinx_salvager")
        record_generation(phase, "/tmp/img.png", "test prompt")
        return phase

    def test_lock_sets_locked_image(self):
        phase = self._phase_with_generation()
        apply_verdict(phase, "lock")
        assert phase.locked_image == "/tmp/img.png"
        assert phase.status == "locked"
        assert phase.generation_history[-1].verdict == "lock"

    def test_hold_adds_to_held_images(self):
        phase = self._phase_with_generation()
        apply_verdict(phase, "hold")
        assert "/tmp/img.png" in phase.held_images
        assert phase.status == "held"
        assert phase.generation_history[-1].verdict == "hold"

    def test_reject_sets_status(self):
        phase = self._phase_with_generation()
        apply_verdict(phase, "reject")
        assert phase.status == "rejected"
        assert phase.generation_history[-1].verdict == "reject"
        assert phase.locked_image is None
        assert "/tmp/img.png" not in phase.held_images

    def test_multiple_holds_accumulate(self):
        phase = PhaseState(phase_id="jinx_salvager")
        record_generation(phase, "/tmp/img1.png", "prompt 1")
        apply_verdict(phase, "hold")
        record_generation(phase, "/tmp/img2.png", "prompt 2")
        apply_verdict(phase, "hold")
        assert len(phase.held_images) == 2
        assert "/tmp/img1.png" in phase.held_images
        assert "/tmp/img2.png" in phase.held_images

    def test_lock_after_hold_keeps_held(self):
        phase = PhaseState(phase_id="jinx_salvager")
        record_generation(phase, "/tmp/img1.png", "prompt 1")
        apply_verdict(phase, "hold")
        record_generation(phase, "/tmp/img2.png", "prompt 2")
        apply_verdict(phase, "lock")
        assert phase.locked_image == "/tmp/img2.png"
        assert "/tmp/img1.png" in phase.held_images
        assert phase.status == "locked"

    def test_verdict_on_empty_phase_raises(self):
        phase = PhaseState(phase_id="jinx_salvager")
        with pytest.raises(ValueError):
            apply_verdict(phase, "lock")
