"""Tests for src/reaper_builder/ — RPPBuilder, rpp_writer."""

import sys
from pathlib import Path

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

import pytest
import tempfile
from src.core.schema import SongDefinition, SongMeta, TrackDef, ClipDef, MidiNote, PluginDef
from src.reaper_builder import RPPBuilder


class TestRPPBuilderWrite:
    """Test RPPBuilder.write() produces valid .rpp output."""

    def test_write_produces_reaper_project_marker(self):
        """RPPBuilder.write() produces a file containing 'REAPER_PROJECT'."""
        meta = SongMeta(bpm=95, key="Am", title="Test")
        song = SongDefinition(meta=meta, tracks=[])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            assert "REAPER_PROJECT" in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)

    def test_write_produces_tempo_line(self):
        """Output contains TEMPO line with correct BPM."""
        meta = SongMeta(bpm=95, key="Am")
        song = SongDefinition(meta=meta, tracks=[])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            assert "TEMPO 95 " in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)


class TestRPPBuilderAudioTrack:
    """Test audio track generates SOURCE WAVE block with correct file path."""

    def test_audio_track_generates_source_wave_block(self):
        """Audio clip produces <SOURCE WAVE> block with FILE path."""
        meta = SongMeta(bpm=95, key="Am")
        clip = ClipDef(
            position=0.0,
            length=16.0,
            name="Kick Loop",
            audio_path="C:/samples/kick.wav",
        )
        track = TrackDef(name="Drums", clips=[clip])
        song = SongDefinition(meta=meta, tracks=[track])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            assert "<SOURCE WAVE\n" in content
            assert 'FILE C:/samples/kick.wav' in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)

    def test_audio_track_includes_track_name(self):
        """Audio track block contains the track NAME."""
        meta = SongMeta(bpm=95, key="Am")
        clip = ClipDef(position=0.0, length=16.0, audio_path="C:/kick.wav")
        track = TrackDef(name="Kick", clips=[clip])
        song = SongDefinition(meta=meta, tracks=[track])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            assert "NAME Kick" in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)


class TestRPPBuilderMidiTrack:
    """Test MIDI track generates MIDI event lines (E lines)."""

    def test_midi_track_generates_e_lines(self):
        """MIDI clip produces E event lines in the output."""
        meta = SongMeta(bpm=95, key="Am")
        note = MidiNote(pitch=36, start=0.0, duration=0.25, velocity=115)
        clip = ClipDef(position=0.0, length=16.0, name="Kick Pattern", midi_notes=[note])
        track = TrackDef(name="Drums", clips=[clip])
        song = SongDefinition(meta=meta, tracks=[track])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            assert "<SOURCE MIDI\n" in content
            assert "E " in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)

    def test_midi_track_note_on_off_pairs(self):
        """MIDI note produces note-on (90) and note-off (80) E lines."""
        meta = SongMeta(bpm=95, key="Am")
        notes = [
            MidiNote(pitch=36, start=0.0, duration=0.25, velocity=115),
            MidiNote(pitch=36, start=1.5, duration=0.25, velocity=105),
        ]
        clip = ClipDef(position=0.0, length=16.0, name="Kick Pattern", midi_notes=notes)
        track = TrackDef(name="Drums", clips=[clip])
        song = SongDefinition(meta=meta, tracks=[track])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            # Note on: status 90, pitch, velocity
            assert "90" in content
            # Note off: status 80, pitch, 00
            assert "80" in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)


class TestRPPBuilderMasterTrack:
    """Test that RPPBuilder includes a master track."""

    def test_output_contains_master_track(self):
        """Generated .rpp contains a master track."""
        meta = SongMeta(bpm=95, key="Am")
        song = SongDefinition(meta=meta, tracks=[])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            assert "NAME master" in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)


class TestRPPProjectFormat:
    """Test output matches the ground truth format from output/test_vst3.rpp."""

    def test_header_version_765_win64(self):
        """REAPER_PROJECT line has version 7.65/win64 (not unquoted 6.0)."""
        meta = SongMeta(bpm=95, key="Am", title="Test")
        song = SongDefinition(meta=meta, tracks=[])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            first_line = content.split('\n', 1)[0]
            # Version must be 7.65/win64, not 6.0
            assert "7.65/win64" in first_line
            # Must NOT contain the old 6.0 version
            assert "6.0" not in first_line
        finally:
            Path(tmp_path).unlink(missing_ok=True)

    def test_peakgain_and_panlaw_present(self):
        """Output contains PEAKGAIN and PANLAW lines from ground truth."""
        meta = SongMeta(bpm=95, key="Am")
        song = SongDefinition(meta=meta, tracks=[])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            assert "PEAKGAIN 1" in content
            assert "PANLAW 1" in content
            assert "SAMPLERATE 44100" in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)

    def test_track_has_all_default_attributes(self):
        """TRACK element contains all 25 default attributes from ground truth."""
        meta = SongMeta(bpm=95, key="Am")
        track = TrackDef(name="Test Track", clips=[])
        song = SongDefinition(meta=meta, tracks=[track])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            # Key attributes that uniquely identify the ground truth format
            assert "PEAKCOL 16576" in content
            assert "BEAT -1" in content
            assert "AUTOMODE 0" in content
            assert "NCHAN 2" in content
            assert "FX 1" in content
            assert "TRACKID {" in content
            assert "VU 64" in content
            assert "INQ 0 0 0 0.5" in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)

    def test_fxchain_has_required_structure(self):
        """FXCHAIN block has WNDRECT, SHOW, BYPASS, FXID lines."""
        meta = SongMeta(bpm=95, key="Am")
        plugin = PluginDef(name="Serum2", path="Serum2.vst3", index=0)
        track = TrackDef(name="Bass", clips=[], plugins=[plugin])
        song = SongDefinition(meta=meta, tracks=[track])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            assert "WNDRECT 24 52 655 408" in content
            assert "SHOW 0" in content
            assert "DOCKED 0" in content
            assert "BYPASS 0 0 0" in content
            assert "FXID {" in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)

    def test_metronome_block_structure(self):
        """METRONOME is a parent element with proper children, not flat attributes."""
        meta = SongMeta(bpm=95, key="Am")
        song = SongDefinition(meta=meta, tracks=[])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            assert "<METRONOME" in content
            assert "PATTERNSTR ABBB" in content
            assert "SAMPLES \"\" \"\" \"\" \"\"" in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)

    def test_master_track_has_fxchain(self):
        """Master track has FXCHAIN block (MASTER_FX 1 requires it)."""
        meta = SongMeta(bpm=95, key="Am")
        song = SongDefinition(meta=meta, tracks=[])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            # Count FXCHAIN blocks - master + any user tracks
            fxchain_count = content.count("<FXCHAIN")
            assert fxchain_count >= 1, f"Expected at least 1 FXCHAIN, got {fxchain_count}"
            # Master track FXCHAIN has master-specific FXID
            assert "FXID {" in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)


class TestVST3GUIDPresence:
    """Test that VST3 plugins output with uniqueid{GUID} tokens."""

    def test_vst3_plugin_output_contains_guid(self):
        """VST3 element contains GUID from registry lookup."""
        meta = SongMeta(bpm=95, key="Am", title="VST3 Test")
        plugin = PluginDef(name="Serum2", path="Serum2.vst3", index=0)
        track = TrackDef(name="Bass", clips=[], plugins=[plugin])
        song = SongDefinition(meta=meta, tracks=[track])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            # Must contain the GUID token from VST3_REGISTRY["Serum2"]
            assert "691258006{56534558667350736572756D20320000}" in content
            # Must also contain correct display name and filename
            assert "VST3i: Serum 2 (Xfer Records)" in content
            assert "Serum2.vst3" in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)

    def test_fabfilter_proq3_contains_guid(self):
        """FabFilter Pro-Q 3 outputs with correct GUID."""
        meta = SongMeta(bpm=95, key="Am", title="VST3 Test")
        plugin = PluginDef(name="FabFilter Pro-Q 3", path="FabFilter Pro-Q 3.vst3", index=0)
        track = TrackDef(name="Lead", clips=[], plugins=[plugin])
        song = SongDefinition(meta=meta, tracks=[track])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            # Must contain the GUID token from VST3_REGISTRY["FabFilter Pro-Q 3"]
            assert "756089518{72C4DB717A4D459AB97E51745D84B39D}" in content
            assert "VST3: Pro-Q 3 (FabFilter)" in content
            # Filename in RPP is "FabFilter" (shared binary for all FabFilter plugins)
            assert "FabFilter 0" in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)


class TestVST3PresetData:
    """Test that VST3 plugins include base64 preset data inside VST blocks."""

    def test_serum2_vst_contains_preset_data(self):
        """Serum2 VST block contains base64 preset lines."""
        meta = SongMeta(bpm=95, key="Am", title="VST3 Preset Test")
        plugin = PluginDef(name="Serum2", path="Serum2.vst3", index=0)
        track = TrackDef(name="Bass", clips=[], plugins=[plugin])
        song = SongDefinition(meta=meta, tracks=[track])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            # Serum2 preset starts with this magic line (first base64 line)
            assert "Z4R+ae5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAbQgAAAEAAAAAAAAA" in content
            # Last line of all presets is the same terminator
            assert "AFByb2dyYW0gMQAAAAAA" in content
            # A mid-preset line (line 2)
            assert "zQQAAAEAAABYZmVySnNvbgC5AAAAAAAAAHsiY29tcG9uZW50IjoicHJvY2Vzc29yIiwiaGFzaCI6IjgxZTEyMWYxNGI2Y2IyYjA2YzMzMjQzZDk1ZDIxYWIxIiwicHJv" in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)

    def test_fabfilter_proq3_vst_contains_preset_data(self):
        """FabFilter Pro-Q 3 VST block contains base64 preset lines."""
        meta = SongMeta(bpm=95, key="Am", title="VST3 Preset Test")
        plugin = PluginDef(name="FabFilter Pro-Q 3", path="FabFilter Pro-Q 3.vst3", index=0)
        track = TrackDef(name="Lead", clips=[], plugins=[plugin])
        song = SongDefinition(meta=meta, tracks=[track])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            # Pro-Q 3 preset starts with this line
            assert "rgIRLe5e7f4EAAAAAQAAAAAAAAACAAAAAAAAAAQAAAAAAAAACAAAAAAAAAACAAAAAQAAAAAAAAACAAAAAAAAAAoGAAABAAAAAAAAAA==" in content
            assert "AFByb2dyYW0gMQAAAAAA" in content
        finally:
            Path(tmp_path).unlink(missing_ok=True)

    def test_registry_plugins_with_preset_data_are_in_output(self):
        """VST3 plugins with non-empty preset data include that data in output.

        New plugins added with empty preset lists ([]) are handled gracefully —
        vst3_element() only appends preset lines when preset_data is truthy.
        """
        meta = SongMeta(bpm=95, key="Am", title="VST3 Preset Test")
        # Use actual filenames from registry so _build_plugin recognizes them as VST3
        from src.reaper_builder import VST3_REGISTRY
        plugins = [
            PluginDef(name=name, path=entry[1], index=i)
            for i, (name, entry) in enumerate(VST3_REGISTRY.items())
        ]
        track = TrackDef(name="Test", clips=[], plugins=plugins)
        song = SongDefinition(meta=meta, tracks=[track])
        builder = RPPBuilder(song)

        with tempfile.NamedTemporaryFile(
            mode="w", suffix=".rpp", delete=False, encoding="utf-8"
        ) as f:
            tmp_path = f.name

        try:
            builder.write(tmp_path)
            content = Path(tmp_path).read_text(encoding="utf-8")
            # Check that plugins WITH preset data have that data in output
            from src.reaper_builder import PLUGIN_PRESETS, VST3_REGISTRY
            vst3_keys = set(VST3_REGISTRY.keys())
            for name, preset_lines in PLUGIN_PRESETS.items():
                # Only check VST3 plugins (skip VST2 plugins which are in the same dict now)
                if name not in vst3_keys:
                    continue
                if len(preset_lines) > 0:
                    # Check first preset line — most distinctive, no collision risk
                    first_line = preset_lines[0]
                    assert first_line in content, f"{name} preset line not found in output"
        finally:
            Path(tmp_path).unlink(missing_ok=True)
