"""Tests for src/reaper_scripting/ — command protocol and ReaScriptGenerator."""

from __future__ import annotations

import ast
import sys
from pathlib import Path
import json
import tempfile

sys.path.insert(0, str(Path(__file__).parents[1]))

import pytest

from src.reaper_scripting.commands import (
    ProtocolVersionError,
    ReaScriptCommand,
    ReaScriptResult,
    read_result,
    write_command,
)
from src.reaper_scripting import ReaScriptGenerator


# ------------------------------------------------------------------
# Phase 1: Command/Result Protocol
# ------------------------------------------------------------------

class TestCommandSerialization:
    """Test JSON round-trip for ReaScriptCommand."""

    def test_write_command_produces_json_file(self, tmp_path):
        cmd = ReaScriptCommand(
            version=1,
            action="calibrate",
            rpp_path="C:/song.rpp",
            render_path="C:/song.wav",
            timeout=120,
            track_calibration=[],
        )
        path = tmp_path / "cmd.json"
        write_command(path, cmd)
        assert path.exists()

    def test_roundtrip_command_fields(self, tmp_path):
        cmd = ReaScriptCommand(
            version=1,
            action="verify_fx",
            rpp_path="C:/song.rpp",
            render_path="C:/song.wav",
            timeout=60,
            track_calibration=[
                {"track_index": 0, "volume": 0.85, "pan": 0.0, "sends": []},
            ],
        )
        path = tmp_path / "cmd.json"
        write_command(path, cmd)
        text = path.read_text(encoding="utf-8")
        data = json.loads(text)
        assert data["version"] == 1
        assert data["action"] == "verify_fx"
        assert data["rpp_path"] == "C:/song.rpp"
        assert data["render_path"] == "C:/song.wav"
        assert data["timeout"] == 60
        assert len(data["track_calibration"]) == 1

    def test_read_result_roundtrip(self, tmp_path):
        cmd = ReaScriptCommand(
            version=1,
            action="calibrate",
            rpp_path="C:/song.rpp",
            render_path="C:/song.wav",
            timeout=120,
            track_calibration=[],
        )
        path = tmp_path / "cmd.json"
        write_command(path, cmd)
        result = read_result(path)
        assert result.version == 1
        assert result.status == "ok"
        assert result.integrated_lufs is None
        assert result.fx_errors == []


class TestVersionMismatch:
    """Test ProtocolVersionError on version mismatch."""

    def test_version_mismatch_raises(self, tmp_path):
        path = tmp_path / "bad_result.json"
        path.write_text(
            json.dumps({"version": 2, "status": "ok", "message": "", "fx_errors": []}),
            encoding="utf-8",
        )
        with pytest.raises(ProtocolVersionError) as exc_info:
            read_result(path)
        assert "expected 1, got 2" in str(exc_info.value)


class TestMissingFile:
    """Test FileNotFoundError on missing file."""

    def test_missing_command_raises_file_not_found(self):
        with pytest.raises(FileNotFoundError):
            read_result(Path("nonexistent.json"))

    def test_missing_result_raises_file_not_found(self, tmp_path):
        nonexistent = tmp_path / "does_not_exist.json"
        with pytest.raises(FileNotFoundError):
            read_result(nonexistent)


# ------------------------------------------------------------------
# Phase 2: ReaScriptGenerator
# ------------------------------------------------------------------

class TestReaScriptGeneratorOutput:
    """Test that ReaScriptGenerator produces valid Python code."""

    def test_generate_produces_file(self, tmp_path):
        cmd = ReaScriptCommand(
            version=1,
            action="calibrate",
            rpp_path="C:/song.rpp",
            render_path="C:/song.wav",
            timeout=120,
            track_calibration=[],
        )
        path = tmp_path / "phase2.py"
        gen = ReaScriptGenerator()
        gen.generate(path, cmd)
        assert path.exists()

    def test_generate_output_is_valid_python(self, tmp_path):
        cmd = ReaScriptCommand(
            version=1,
            action="calibrate",
            rpp_path="C:/song.rpp",
            render_path="C:/song.wav",
            timeout=120,
            track_calibration=[],
        )
        path = tmp_path / "phase2.py"
        gen = ReaScriptGenerator()
        gen.generate(path, cmd)
        source = path.read_text(encoding="utf-8")
        # Must not raise
        tree = ast.parse(source)
        assert tree is not None

    def test_contains_required_api_calls(self, tmp_path):
        cmd = ReaScriptCommand(
            version=1,
            action="calibrate",
            rpp_path="C:/song.rpp",
            render_path="C:/song.wav",
            timeout=120,
            track_calibration=[],
        )
        path = tmp_path / "phase2.py"
        gen = ReaScriptGenerator()
        gen.generate(path, cmd)
        source = path.read_text(encoding="utf-8")
        required = [
            "Main_openProject",
            "TrackFX_GetCount",
            "TrackFX_GetFXName",
            "SetMediaTrackInfo_Value",
            "CreateTrackSend",
            "Main_RenderFile",
            "CalcMediaSrcLoudness",
            "GetFunctionMetadata",
        ]
        for api in required:
            assert api in source, f"{api} not found in generated script"

    def test_script_reads_command_path(self, tmp_path):
        cmd = ReaScriptCommand(
            version=1,
            action="calibrate",
            rpp_path="C:/song.rpp",
            render_path="C:/song.wav",
            timeout=120,
            track_calibration=[],
        )
        path = tmp_path / "phase2.py"
        gen = ReaScriptGenerator()
        gen.generate(path, cmd)
        source = path.read_text(encoding="utf-8")
        # Must reference the command JSON path
        assert "fl_control_command.json" in source
        assert "fl_control_result.json" in source

    def test_script_has_handrolled_json_parser(self, tmp_path):
        cmd = ReaScriptCommand(
            version=1,
            action="calibrate",
            rpp_path="C:/song.rpp",
            render_path="C:/song.wav",
            timeout=120,
            track_calibration=[],
        )
        path = tmp_path / "phase2.py"
        gen = ReaScriptGenerator()
        gen.generate(path, cmd)
        source = path.read_text(encoding="utf-8")
        # Must NOT use Python's json module
        assert "import json" not in source
        # Must have hand-rolled parser
        assert "parse_json" in source
        assert "write_json" in source

    def test_script_includes_api_check_function(self, tmp_path):
        cmd = ReaScriptCommand(
            version=1,
            action="verify_fx",
            rpp_path="C:/song.rpp",
            render_path="C:/song.wav",
            timeout=120,
            track_calibration=[],
        )
        path = tmp_path / "phase2.py"
        gen = ReaScriptGenerator()
        gen.generate(path, cmd)
        source = path.read_text(encoding="utf-8")
        assert "check_api" in source
        assert "GetFunctionMetadata" in source

    def test_verify_fx_action_iterates_tracks(self, tmp_path):
        cmd = ReaScriptCommand(
            version=1,
            action="verify_fx",
            rpp_path="C:/song.rpp",
            render_path="",
            timeout=60,
            track_calibration=[],
        )
        path = tmp_path / "phase2.py"
        gen = ReaScriptGenerator()
        gen.generate(path, cmd)
        source = path.read_text(encoding="utf-8")
        # Must iterate all tracks to verify FX
        assert "CountTracks" in source
        assert "GetTrack" in source
        assert "fx_errors" in source

    def test_calibrate_action_sets_track_volume_pan(self, tmp_path):
        cmd = ReaScriptCommand(
            version=1,
            action="calibrate",
            rpp_path="C:/song.rpp",
            render_path="C:/song.wav",
            timeout=120,
            track_calibration=[
                {"track_index": 0, "volume": 0.85, "pan": 0.0, "sends": []},
                {
                    "track_index": 1,
                    "volume": 0.7,
                    "pan": -0.3,
                    "sends": [{"dest_track_index": 5, "level": 0.05}],
                },
            ],
        )
        path = tmp_path / "phase2.py"
        gen = ReaScriptGenerator()
        gen.generate(path, cmd)
        source = path.read_text(encoding="utf-8")
        # Volume and pan setting
        assert "D_VOL" in source
        assert "D_PAN" in source
        # Send creation
        assert "CreateTrackSend" in source

    def test_error_handling_writes_error_result(self, tmp_path):
        cmd = ReaScriptCommand(
            version=1,
            action="calibrate",
            rpp_path="C:/song.rpp",
            render_path="C:/song.wav",
            timeout=120,
            track_calibration=[],
        )
        path = tmp_path / "phase2.py"
        gen = ReaScriptGenerator()
        gen.generate(path, cmd)
        source = path.read_text(encoding="utf-8")
        # Must handle errors gracefully and write error status
        assert '"status": "error"' in source or "'status': 'error'" in source
        assert "write_json" in source

    def test_script_has_main_function(self, tmp_path):
        cmd = ReaScriptCommand(
            version=1,
            action="calibrate",
            rpp_path="C:/song.rpp",
            render_path="C:/song.wav",
            timeout=120,
            track_calibration=[],
        )
        path = tmp_path / "phase2.py"
        gen = ReaScriptGenerator()
        gen.generate(path, cmd)
        source = path.read_text(encoding="utf-8")
        assert "def main():" in source
        assert "main()" in source
