"""Tests for role-aware preset system (presets-pack).

Covers PresetTransformer, role-aware PLUGIN_PRESETS lookup,
and thread-through via make_plugin → PluginDef.role → _build_plugin.
"""

from __future__ import annotations

import base64
import json

import pytest

from src.reaper_builder.preset_transformer import PresetTransformer
from src.reaper_builder import PLUGIN_PRESETS, PLUGIN_REGISTRY


# ---------------------------------------------------------------------------
# PresetTransformer unit tests
# ---------------------------------------------------------------------------

class TestPresetTransformer:
    """PresetTransformer derives role-specific preset data."""

    def test_derive_serum_returns_list(self):
        """derive() for Serum_2 returns a list of chunks."""
        default = PLUGIN_PRESETS.get(("Serum_2", ""))
        assert default is not None, "Serum_2 default preset must exist"
        result = PresetTransformer.derive("Serum_2", default, "bass")
        assert isinstance(result, list)
        assert len(result) == len(default)
        assert all(isinstance(c, str) for c in result)

    def test_derive_decapitator_returns_list(self):
        """derive() for Decapitator returns a list of chunks."""
        default = PLUGIN_PRESETS.get(("Decapitator", ""))
        assert default is not None, "Decapitator default preset must exist"
        result = PresetTransformer.derive("Decapitator", default, "drums")
        assert isinstance(result, list)
        assert len(result) == len(default)

    def test_derive_omnisphere_returns_list(self):
        """derive() for Omnisphere returns a list of chunks."""
        default = PLUGIN_PRESETS.get(("Omnisphere", ""))
        assert default is not None, "Omnisphere default preset must exist"
        result = PresetTransformer.derive("Omnisphere", default, "pad")
        assert isinstance(result, list)
        assert len(result) == len(default)

    def test_derive_unknown_plugin_returns_default(self):
        """derive() for unsupported plugin returns original chunks."""
        chunks = ["mock_chunk_1", "mock_chunk_2"]
        result = PresetTransformer.derive("NonexistentPlugin", chunks, "lead")
        assert result == chunks

    def test_derive_preserves_chunk_count(self):
        """derive() output has same number of chunks as input."""
        for plugin in ["Serum_2", "Decapitator", "Omnisphere"]:
            default = PLUGIN_PRESETS.get((plugin, ""))
            if not default:
                continue
            for role in ["bass", "lead", "drums", "pad"]:
                result = PresetTransformer.derive(plugin, default, role)
                assert len(result) == len(default), (
                    f"{plugin}/{role} chunk count mismatch: "
                    f"got {len(result)}, expected {len(default)}"
                )


# ---------------------------------------------------------------------------
# Role-aware preset structure tests
# ---------------------------------------------------------------------------

class TestRoleAwarePresets:
    """PLUGIN_PRESETS is structured as {(plugin, role): chunks}."""

    def test_default_role_entries_exist(self):
        """All known plugins have a "" (default) role entry."""
        flat_keys = set()
        for (name, role), _ in PLUGIN_PRESETS.items():
            if role == "":
                flat_keys.add(name)
        # At minimum, the multi-role targets must have defaults
        for name in ["Serum_2", "Decapitator", "Omnisphere"]:
            assert name in flat_keys, f"{name} must have default role entry"

    def test_role_specific_entries_exist(self):
        """Multi-role plugins have their role-specific entries."""
        roles = {
            "Serum_2":     ["bass", "lead"],
            "Decapitator": ["drumloop", "bass", "clap", "perc"],
            "Omnisphere":  ["chords", "pad"],
        }
        for plugin, expected_roles in roles.items():
            for role in expected_roles:
                assert (plugin, role) in PLUGIN_PRESETS, (
                    f"Missing ({plugin}, {role}) in PLUGIN_PRESETS"
                )

    def test_role_entries_non_empty(self):
        """Role-specific entries contain non-empty preset data."""
        for (name, role), chunks in PLUGIN_PRESETS.items():
            if role != "" and name in ("Serum_2", "Decapitator", "Omnisphere"):
                assert len(chunks) > 0, (
                    f"({name}, {role}) has empty preset data"
                )

    def test_unknown_role_falls_back_to_default(self):
        """Role not present in PLUGIN_PRESETS → fall back to default."""
        # Simulate: a plugin has only default entry, no "pad" role
        # The lookup should return the "" entry
        from src.reaper_builder import _resolve_preset

        # Gullfoss_Master doesn't have multi-role entries, only ""
        result = _resolve_preset("Gullfoss_Master", "pad")
        default = PLUGIN_PRESETS.get(("Gullfoss_Master", ""))
        assert result == default, (
            "Unknown role should fall back to default preset"
        )


# ---------------------------------------------------------------------------
# Integration: make_plugin + role threading
# ---------------------------------------------------------------------------

class TestMakePluginRoleThreading:
    """make_plugin with role correctly sets PluginDef.role and preset_data."""

    def test_make_plugin_with_role_sets_role_field(self):
        """make_plugin(key, idx, role=...) sets PluginDef.role."""
        from scripts.compose import make_plugin

        p = make_plugin("Serum_2", 0, role="bass")
        assert p.role == "bass"
        assert p.preset_data is not None

    def test_make_plugin_without_role_defaults_to_empty(self):
        """make_plugin(key, idx) without role sets role="" (backward compat)."""
        from scripts.compose import make_plugin

        p = make_plugin("Decapitator", 0)
        assert p.role == ""

    def test_make_plugin_different_roles_lookup_correct_entries(self):
        """Different roles resolve to their respective PLUGIN_PRESETS entries."""
        from scripts.compose import make_plugin

        # Both should return data — in MVP they're identical but structure is correct
        p_bass = make_plugin("Serum_2", 0, role="bass")
        p_lead = make_plugin("Serum_2", 0, role="lead")

        # Both should have non-None preset data
        assert p_bass.preset_data is not None, "bass role should have preset_data"
        assert p_lead.preset_data is not None, "lead role should have preset_data"

        # Both should be lists with content
        assert isinstance(p_bass.preset_data, list)
        assert isinstance(p_lead.preset_data, list)
        assert len(p_bass.preset_data) > 0
        assert len(p_lead.preset_data) > 0

    def test_make_plugin_unknown_role_falls_back(self):
        """Unknown role returns preset from "" entry (if available)."""
        from scripts.compose import make_plugin

        # "pad" role is not valid for Serum_2 (Omnisphere handles pad)
        p = make_plugin("Serum_2", 0, role="pad")
        # Should still get the default Serum_2 preset
        assert p.preset_data is not None
        assert p.role == "pad"


# ---------------------------------------------------------------------------
# Backward compatibility
# ---------------------------------------------------------------------------

class TestBackwardCompatibility:
    """Existing behavior preserved with no role."""

    def test_make_plugin_known_key_still_works(self):
        """make_plugin with known registry key (no role) works as before."""
        from scripts.compose import make_plugin
        p = make_plugin("Decapitator", 0)
        assert p.name == "Decapitator"
        assert p.index == 0
        assert p.role == ""

    def test_make_plugin_unknown_key_still_works(self):
        """make_plugin with unknown key (no role) returns PluginDef."""
        from scripts.compose import make_plugin
        p = make_plugin("NonExistent", 2)
        assert p.name == "NonExistent"
        assert p.index == 2
        assert p.role == ""
