"""Integration tests for scripts/compose.py — end-to-end compose workflow."""

import sys
from pathlib import Path
from unittest.mock import patch, MagicMock

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

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


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def compose_via_builder(
    genre: str = "reggaeton",
    bpm: float = 95.0,
    key: str = "Am",
    output_path: str = "output/track.rpp",
) -> SongDefinition:
    """Build a SongDefinition the same way scripts/compose.py does, return it.

    This lets us test the compose logic without hitting the filesystem for samples.
    """
    import json
    from pathlib import Path as P

    _ROOT = P(__file__).parent.parent

    from src.composer.rhythm import get_notes
    from src.composer.melodic import bass_tresillo, lead_hook, chords_block, pad_sustain
    from src.composer.converters import rhythm_to_midi, melodic_to_midi

    genre_path = _ROOT / "knowledge" / "genres" / f"{genre.lower()}_2009.json"
    with open(genre_path, "r", encoding="utf-8") as f:
        genre_config = json.load(f)

    from scripts.compose import (
        build_section_tracks, create_return_tracks, EFFECT_ALIASES,
        build_fx_chain, build_sampler_plugin,
    )
    from src.selector import SampleSelector

    index_path = _ROOT / "data" / "sample_index.json"
    selector = SampleSelector(str(index_path))

    tracks, sections = build_section_tracks(genre_config, selector, key, bpm)
    return_tracks = create_return_tracks()

    meta = SongMeta(bpm=bpm, key=key, title=f"{genre.capitalize()} Track")
    return SongDefinition(meta=meta, tracks=tracks + return_tracks, sections=sections)


# ---------------------------------------------------------------------------
# Tests
# ---------------------------------------------------------------------------

class TestComposeRppOutput:
    """Tests for compose workflow producing valid .rpp output."""

    def test_compose_produces_rpp_file(self, tmp_path):
        """main() with valid args produces a .rpp file at the output path."""
        output = tmp_path / "track.rpp"

        # Mock SampleSelector.select_one so we don't need actual sample files
        with patch("scripts.compose.SampleSelector") as mock_selector_cls:
            mock_selector = MagicMock()
            mock_selector.select_one.return_value = None  # audio_path stays None
            mock_selector_cls.return_value = mock_selector

            from scripts.compose import main
            import sys
            original_argv = sys.argv
            try:
                sys.argv = [
                    "compose",
                    "--genre", "reggaeton",
                    "--bpm", "95",
                    "--key", "Am",
                    "--output", str(output),
                ]
                main()
            finally:
                sys.argv = original_argv

        assert output.exists(), f"Expected {output} to exist"

    def test_compose_rpp_has_min_6_tracks(self, tmp_path):
        """The .rpp output contains at least 6 <TRACK blocks (roles + 2 returns)."""
        output = tmp_path / "track.rpp"

        with patch("scripts.compose.SampleSelector") as mock_selector_cls:
            mock_selector = MagicMock()
            mock_selector.select_one.return_value = None
            mock_selector_cls.return_value = mock_selector

            from scripts.compose import main
            import sys
            original_argv = sys.argv
            try:
                sys.argv = [
                    "compose",
                    "--genre", "reggaeton",
                    "--bpm", "95",
                    "--key", "Am",
                    "--output", str(output),
                ]
                main()
            finally:
                sys.argv = original_argv

        content = output.read_text(encoding="utf-8")
        track_count = content.count("<TRACK")
        # 6 roles + 2 return tracks = 8 minimum
        assert track_count >= 6, f"Expected >= 6 tracks, got {track_count}"

    def test_compose_has_fxchain(self, tmp_path):
        """The .rpp output contains FXCHAIN elements."""
        output = tmp_path / "track.rpp"

        with patch("scripts.compose.SampleSelector") as mock_selector_cls:
            mock_selector = MagicMock()
            mock_selector.select_one.return_value = None
            mock_selector_cls.return_value = mock_selector

            from scripts.compose import main
            import sys
            original_argv = sys.argv
            try:
                sys.argv = [
                    "compose",
                    "--genre", "reggaeton",
                    "--bpm", "95",
                    "--key", "Am",
                    "--output", str(output),
                ]
                main()
            finally:
                sys.argv = original_argv

        content = output.read_text(encoding="utf-8")
        assert "FXCHAIN" in content, "Expected FXCHAIN in output"

    def test_compose_invalid_bpm_raises(self):
        """main() with bpm=0 raises ValueError."""
        from scripts.compose import main
        import sys
        original_argv = sys.argv
        try:
            sys.argv = [
                "compose",
                "--genre", "reggaeton",
                "--bpm", "0",
                "--key", "Am",
                "--output", "output/track.rpp",
            ]
            with pytest.raises(ValueError, match="bpm must be > 0"):
                main()
        finally:
            sys.argv = original_argv

    def test_compose_negative_bpm_raises(self):
        """main() with bpm=-10 raises ValueError."""
        from scripts.compose import main
        import sys
        original_argv = sys.argv
        try:
            sys.argv = [
                "compose",
                "--genre", "reggaeton",
                "--bpm", "-10",
                "--key", "Am",
                "--output", "output/track.rpp",
            ]
            with pytest.raises(ValueError, match="bpm must be > 0"):
                main()
        finally:
            sys.argv = original_argv


class TestSectionBuilderIntegration:
    """Test section builder integration with SongDefinition."""

    def test_build_section_tracks_returns_tracks_and_sections(self):
        """build_section_tracks returns (tracks, sections) tuple."""
        import json
        from pathlib import Path as P

        _ROOT = P(__file__).parent.parent
        from scripts.compose import build_section_tracks
        from src.selector import SampleSelector

        genre_path = _ROOT / "knowledge" / "genres" / "reggaeton_2009.json"
        with open(genre_path, "r", encoding="utf-8") as f:
            genre_config = json.load(f)

        index_path = _ROOT / "data" / "sample_index.json"
        selector = SampleSelector(str(index_path))

        # Pass explicit sections_data since JSON now uses templates format
        sections_data = genre_config.get("structure", {}).get("templates", {}).get("extracted_real_tracks", [])
        tracks, sections = build_section_tracks(genre_config, selector, "Am", 95.0, sections_data=sections_data)

        assert len(tracks) > 0, "Expected at least one track"
        assert len(sections) > 0, "Expected at least one section"
        # Sections should have names from the genre config
        valid_names = {"intro", "verse", "build", "pre_chorus", "chorus", "drop",
                       "break", "gap", "bridge", "outro", "verse2", "chorus2", "chorus3"}
        for sec in sections:
            assert sec.name in valid_names, f"Unexpected section name: {sec.name}"

    def test_song_definition_has_sections_field(self):
        """SongDefinition has a sections field."""
        from src.core.schema import SongDefinition, SongMeta, SectionDef

        meta = SongMeta(bpm=95, key="Am")
        song = SongDefinition(
            meta=meta,
            tracks=[],
            sections=[SectionDef(name="intro", bars=4, energy=0.3)],
        )
        assert len(song.sections) == 1
        assert song.sections[0].name == "intro"