import os
import json
import pytest
import numpy as np
from unittest.mock import patch, MagicMock

from app.core.vst_engine import PluginManager, midi_note_to_freq, render_midi_events_to_audio, HAS_PEDALBOARD


class TestPluginManager:
    def test_init(self):
        pm = PluginManager()
        if os.name == "nt":
            pf = os.environ.get("ProgramFiles", r"C:\Program Files")
            assert pm.vst_dir == os.path.join(pf, "Common Files", "VST3")
            root = os.environ.get("APPDATA") or os.path.expanduser("~")
            assert pm.sf_dir == os.path.join(root, "SonicForgeDAW", "soundfonts")
        else:
            assert pm.vst_dir == "/opt/daw_engine/vst3"
            assert pm.sf_dir == "/opt/daw_engine/soundfonts"

    def test_midi_events_to_messages(self):
        if not HAS_PEDALBOARD:
            pytest.skip("pedalboard not available")
        events = [{"note": 60, "start_beat": 0, "duration_beats": 1, "velocity": 100}]
        bpm = 120
        sr = 44100
        msgs = PluginManager.midi_events_to_messages(events, bpm, sr)
        beat_sec = 60.0 / 120
        # pedalboard >= 0.9: message = (bytes raw MIDI, timestamp_seconds)
        assert msgs[0] == (bytes([0x90, 60, 100]), 0.0)
        assert msgs[1] == (bytes([0x80, 60, 0]), beat_sec)

    def test_list_available_empty(self):
        pm = PluginManager(vst_dir="/tmp/nonexistent_vst_dir_xyz", sf_dir="/tmp/nonexistent_sf_dir_xyz")
        available = pm.list_available()
        assert "vst_instruments" in available
        assert "soundfonts" in available
        assert available["vst_instruments"] == []
        assert available["soundfonts"] == []

    def test_validate_sf2_header_valid(self):
        # Valid RIFF + sfbk header
        valid = b'RIFF' + b'\x00' * 4 + b'sfbk' + b'\x00' * 100
        assert PluginManager.validate_sf2_header(valid) is True

    def test_validate_sf2_header_invalid_no_riff(self):
        invalid = b'XXXX' + b'\x00' * 4 + b'sfbk' + b'\x00' * 100
        assert PluginManager.validate_sf2_header(invalid) is False

    def test_validate_sf2_header_invalid_no_sfbk(self):
        invalid = b'RIFF' + b'\x00' * 4 + b'XXXX' + b'\x00' * 100
        assert PluginManager.validate_sf2_header(invalid) is False

    def test_validate_sf2_header_too_short(self):
        assert PluginManager.validate_sf2_header(b'RIFF') is False


class TestMidiNoteToFreq:
    def test_a4_440(self):
        assert midi_note_to_freq(69) == 440.0

    def test_c4(self):
        # C4 = MIDI 60 = 261.63
        freq = midi_note_to_freq(60)
        assert abs(freq - 261.63) < 0.5

    def test_note_zero_to_freq(self):
        freq = midi_note_to_freq(0)
        assert freq > 0 and freq < 10


class TestRenderMidiToAudio:
    def test_render_single_note(self):
        events = [{"note": 60, "start_beat": 0, "duration_beats": 1, "velocity": 100}]
        audio = render_midi_events_to_audio(events, sr=44100, bpm=120)
        assert audio.shape[0] == 2  # Stereo
        assert audio.shape[1] > 0
        # Should have non-zero samples
        assert np.max(np.abs(audio)) > 0

    def test_render_empty_events(self):
        audio = render_midi_events_to_audio([], sr=44100, bpm=120)
        assert audio.shape[0] == 2  # Stereo
        # Empty events defaults to 2 seconds of silence (minimum length)
        assert audio.shape[1] >= 0

    def test_render_multiple_notes(self):
        events = [
            {"note": 60, "start_beat": 0, "duration_beats": 1, "velocity": 100},
            {"note": 64, "start_beat": 1, "duration_beats": 1, "velocity": 80},
            {"note": 67, "start_beat": 2, "duration_beats": 1, "velocity": 90},
        ]
        audio = render_midi_events_to_audio(events, sr=44100, bpm=120)
        assert audio.shape[0] == 2
        assert audio.shape[1] > 44100 * 1
