"""Unit tests for symlink utilities in voice_mode.utils.symlinks."""

import os
from pathlib import Path
from unittest.mock import patch, MagicMock
import pytest


@pytest.fixture(autouse=True)
def patch_audio_dir(isolate_home_directory, monkeypatch):
    """Ensure AUDIO_DIR points to the current test's home directory.

    AUDIO_DIR is computed at module import time from Path.home(), so it gets
    baked in from whichever test first triggers the import. This fixture
    patches it to use the current test's isolated home directory.
    """
    correct_audio_dir = isolate_home_directory / ".voicemode" / "audio"
    monkeypatch.setattr("voice_mode.config.AUDIO_DIR", correct_audio_dir)
    monkeypatch.setattr("voice_mode.utils.symlinks.AUDIO_DIR", correct_audio_dir)


class TestUpdateLatestSymlinks:
    """Tests for update_latest_symlinks function."""

    def test_creates_stt_and_latest_symlinks_for_wav(self, isolate_home_directory):
        """Test that symlinks are created correctly for .wav STT files."""
        from voice_mode.utils.symlinks import update_latest_symlinks
        from voice_mode.config import AUDIO_DIR

        # Create test audio file
        AUDIO_DIR.mkdir(parents=True, exist_ok=True)
        year_month = AUDIO_DIR / "2026" / "02"
        year_month.mkdir(parents=True, exist_ok=True)
        test_file = year_month / "123456_conv1_stt.wav"
        test_file.write_bytes(b"fake audio data")

        # Call the function
        type_symlink, latest_symlink = update_latest_symlinks(test_file, "stt")

        # Verify symlinks were created
        assert type_symlink is not None
        assert latest_symlink is not None
        assert type_symlink.name == "latest-STT.wav"
        assert latest_symlink.name == "latest.wav"

        # Verify symlinks point to correct file
        assert type_symlink.is_symlink()
        assert latest_symlink.is_symlink()
        assert type_symlink.resolve() == test_file.resolve()
        assert latest_symlink.resolve() == test_file.resolve()

    def test_creates_tts_and_latest_symlinks_for_mp3(self, isolate_home_directory):
        """Test that symlinks are created correctly for .mp3 TTS files."""
        from voice_mode.utils.symlinks import update_latest_symlinks
        from voice_mode.config import AUDIO_DIR

        # Create test audio file
        AUDIO_DIR.mkdir(parents=True, exist_ok=True)
        year_month = AUDIO_DIR / "2026" / "02"
        year_month.mkdir(parents=True, exist_ok=True)
        test_file = year_month / "123456_conv1_tts.mp3"
        test_file.write_bytes(b"fake mp3 data")

        # Call the function
        type_symlink, latest_symlink = update_latest_symlinks(test_file, "tts")

        # Verify symlinks were created
        assert type_symlink is not None
        assert latest_symlink is not None
        assert type_symlink.name == "latest-TTS.mp3"
        assert latest_symlink.name == "latest.mp3"

        # Verify symlinks point to correct file
        assert type_symlink.resolve() == test_file.resolve()
        assert latest_symlink.resolve() == test_file.resolve()

    def test_creates_symlinks_for_various_extensions(self, isolate_home_directory):
        """Test symlink creation works for multiple audio formats."""
        from voice_mode.utils.symlinks import update_latest_symlinks
        from voice_mode.config import AUDIO_DIR

        AUDIO_DIR.mkdir(parents=True, exist_ok=True)

        extensions = [".wav", ".mp3", ".flac", ".aac", ".opus", ".ogg"]

        for ext in extensions:
            test_file = AUDIO_DIR / f"test_audio{ext}"
            test_file.write_bytes(b"fake audio data")

            type_symlink, latest_symlink = update_latest_symlinks(test_file, "stt")

            assert type_symlink is not None, f"Failed for extension {ext}"
            assert latest_symlink is not None, f"Failed for extension {ext}"
            assert type_symlink.name == f"latest-STT{ext}"
            assert latest_symlink.name == f"latest{ext}"

            # Clean up for next iteration
            test_file.unlink()
            type_symlink.unlink()
            latest_symlink.unlink()

    def test_uses_relative_paths_for_symlinks(self, isolate_home_directory):
        """Test that symlinks use relative paths when file is under AUDIO_DIR."""
        from voice_mode.utils.symlinks import update_latest_symlinks
        from voice_mode.config import AUDIO_DIR

        # Create test audio file in subdirectory
        AUDIO_DIR.mkdir(parents=True, exist_ok=True)
        year_month = AUDIO_DIR / "2026" / "02"
        year_month.mkdir(parents=True, exist_ok=True)
        test_file = year_month / "test_relative.wav"
        test_file.write_bytes(b"fake audio data")

        type_symlink, latest_symlink = update_latest_symlinks(test_file, "stt")

        # Read the symlink target (without resolving)
        stt_target = os.readlink(type_symlink)
        latest_target = os.readlink(latest_symlink)

        # Should be relative path like "2026/02/test_relative.wav"
        assert not os.path.isabs(stt_target), "STT symlink should use relative path"
        assert not os.path.isabs(latest_target), "latest symlink should use relative path"
        assert stt_target == "2026/02/test_relative.wav"
        assert latest_target == "2026/02/test_relative.wav"


class TestSymlinkExtensionChanges:
    """Tests for symlink updates when file extension changes."""

    def test_removes_old_stt_symlink_when_extension_changes(self, isolate_home_directory):
        """Test that old STT symlink is removed when a new file has different extension."""
        from voice_mode.utils.symlinks import update_latest_symlinks
        from voice_mode.config import AUDIO_DIR

        AUDIO_DIR.mkdir(parents=True, exist_ok=True)

        # Create first file with .wav extension
        wav_file = AUDIO_DIR / "first_stt.wav"
        wav_file.write_bytes(b"wav data")
        update_latest_symlinks(wav_file, "stt")

        # Verify initial symlinks exist
        old_stt_symlink = AUDIO_DIR / "latest-STT.wav"
        old_latest_symlink = AUDIO_DIR / "latest.wav"
        assert old_stt_symlink.is_symlink()
        assert old_latest_symlink.is_symlink()

        # Create second file with .mp3 extension
        mp3_file = AUDIO_DIR / "second_stt.mp3"
        mp3_file.write_bytes(b"mp3 data")
        update_latest_symlinks(mp3_file, "stt")

        # Old symlinks should be removed
        assert not old_stt_symlink.exists(), "Old .wav STT symlink should be removed"
        assert not old_latest_symlink.exists(), "Old .wav latest symlink should be removed"

        # New symlinks should exist
        new_stt_symlink = AUDIO_DIR / "latest-STT.mp3"
        new_latest_symlink = AUDIO_DIR / "latest.mp3"
        assert new_stt_symlink.is_symlink()
        assert new_latest_symlink.is_symlink()
        assert new_stt_symlink.resolve() == mp3_file.resolve()
        assert new_latest_symlink.resolve() == mp3_file.resolve()

    def test_removes_old_tts_symlink_when_extension_changes(self, isolate_home_directory):
        """Test that old TTS symlink is removed when a new file has different extension."""
        from voice_mode.utils.symlinks import update_latest_symlinks
        from voice_mode.config import AUDIO_DIR

        AUDIO_DIR.mkdir(parents=True, exist_ok=True)

        # Create first file with .mp3 extension
        mp3_file = AUDIO_DIR / "first_tts.mp3"
        mp3_file.write_bytes(b"mp3 data")
        update_latest_symlinks(mp3_file, "tts")

        # Create second file with .flac extension
        flac_file = AUDIO_DIR / "second_tts.flac"
        flac_file.write_bytes(b"flac data")
        update_latest_symlinks(flac_file, "tts")

        # Old symlinks should be removed
        assert not (AUDIO_DIR / "latest-TTS.mp3").exists()
        assert not (AUDIO_DIR / "latest.mp3").exists()

        # New symlinks should exist
        assert (AUDIO_DIR / "latest-TTS.flac").is_symlink()
        assert (AUDIO_DIR / "latest.flac").is_symlink()

    def test_latest_symlink_updated_by_both_stt_and_tts(self, isolate_home_directory):
        """Test that 'latest' symlink is updated by both STT and TTS."""
        from voice_mode.utils.symlinks import update_latest_symlinks
        from voice_mode.config import AUDIO_DIR

        AUDIO_DIR.mkdir(parents=True, exist_ok=True)

        # Create STT file
        stt_file = AUDIO_DIR / "recording.wav"
        stt_file.write_bytes(b"stt data")
        update_latest_symlinks(stt_file, "stt")

        assert (AUDIO_DIR / "latest.wav").resolve() == stt_file.resolve()

        # Create TTS file (should update latest)
        tts_file = AUDIO_DIR / "speech.mp3"
        tts_file.write_bytes(b"tts data")
        update_latest_symlinks(tts_file, "tts")

        # latest should now point to TTS file
        assert (AUDIO_DIR / "latest.mp3").resolve() == tts_file.resolve()
        assert not (AUDIO_DIR / "latest.wav").exists()

        # STT-specific symlink should still point to STT file
        assert (AUDIO_DIR / "latest-STT.wav").resolve() == stt_file.resolve()
        # TTS-specific symlink should point to TTS file
        assert (AUDIO_DIR / "latest-TTS.mp3").resolve() == tts_file.resolve()


class TestSymlinkCleanup:
    """Tests for cleanup of old symlinks."""

    def test_removes_only_symlinks_not_regular_files(self, isolate_home_directory, tmp_path):
        """Test that cleanup only removes symlinks, not regular files."""
        from voice_mode.utils.symlinks import _remove_old_symlinks

        # Use a fresh directory to avoid conflicts with other tests
        test_dir = tmp_path / "symlink_cleanup_test"
        test_dir.mkdir(parents=True, exist_ok=True)

        # Create a regular file that matches the pattern
        regular_file = test_dir / "latest-STT.txt"
        regular_file.write_text("not a symlink")

        # Create a symlink that matches the pattern
        symlink_target = test_dir / "target.wav"
        symlink_target.write_bytes(b"target")
        symlink = test_dir / "latest-STT.wav"
        symlink.symlink_to(symlink_target)

        # Run cleanup
        _remove_old_symlinks(test_dir, "latest-STT")

        # Regular file should still exist
        assert regular_file.exists(), "Regular file should not be removed"
        # Symlink should be removed
        assert not symlink.exists(), "Symlink should be removed"

    def test_cleanup_removes_symlink_with_different_extension(self, isolate_home_directory, tmp_path):
        """Test that cleanup removes symlinks with various extensions."""
        from voice_mode.utils.symlinks import _remove_old_symlinks

        # Use a fresh directory
        test_dir = tmp_path / "symlink_ext_test"
        test_dir.mkdir(parents=True, exist_ok=True)

        # Create target file
        target = test_dir / "target.wav"
        target.write_bytes(b"target")

        # Create symlinks with different extensions
        symlink_wav = test_dir / "latest-STT.wav"
        symlink_mp3 = test_dir / "latest-STT.mp3"
        symlink_wav.symlink_to(target)
        symlink_mp3.symlink_to(target)

        # Run cleanup
        _remove_old_symlinks(test_dir, "latest-STT")

        # Both symlinks should be removed
        assert not symlink_wav.exists()
        assert not symlink_mp3.exists()


class TestSymlinkErrorHandling:
    """Tests for error handling in symlink operations."""

    def test_returns_none_for_nonexistent_file(self, isolate_home_directory):
        """Test that function returns (None, None) for nonexistent source file."""
        from voice_mode.utils.symlinks import update_latest_symlinks
        from voice_mode.config import AUDIO_DIR

        AUDIO_DIR.mkdir(parents=True, exist_ok=True)
        nonexistent = AUDIO_DIR / "does_not_exist.wav"

        type_symlink, latest_symlink = update_latest_symlinks(nonexistent, "stt")

        assert type_symlink is None
        assert latest_symlink is None

    def test_returns_none_for_file_without_extension(self, isolate_home_directory):
        """Test that function returns (None, None) for file without extension."""
        from voice_mode.utils.symlinks import update_latest_symlinks
        from voice_mode.config import AUDIO_DIR

        AUDIO_DIR.mkdir(parents=True, exist_ok=True)
        no_ext_file = AUDIO_DIR / "no_extension"
        no_ext_file.write_bytes(b"data")

        type_symlink, latest_symlink = update_latest_symlinks(no_ext_file, "stt")

        assert type_symlink is None
        assert latest_symlink is None

    def test_handles_permission_error_gracefully(self, isolate_home_directory):
        """Test that permission errors are handled and return (None, None)."""
        from voice_mode.utils.symlinks import update_latest_symlinks
        from voice_mode.config import AUDIO_DIR

        AUDIO_DIR.mkdir(parents=True, exist_ok=True)
        test_file = AUDIO_DIR / "test.wav"
        test_file.write_bytes(b"data")

        # Mock symlink_to to raise permission error
        with patch.object(Path, "symlink_to", side_effect=OSError("Permission denied")):
            type_symlink, latest_symlink = update_latest_symlinks(test_file, "stt")

        assert type_symlink is None
        assert latest_symlink is None

    def test_handles_string_path_input(self, isolate_home_directory):
        """Test that function accepts string paths in addition to Path objects."""
        from voice_mode.utils.symlinks import update_latest_symlinks
        from voice_mode.config import AUDIO_DIR

        AUDIO_DIR.mkdir(parents=True, exist_ok=True)
        test_file = AUDIO_DIR / "test_string_path.wav"
        test_file.write_bytes(b"data")

        # Pass string path instead of Path object
        type_symlink, latest_symlink = update_latest_symlinks(str(test_file), "stt")

        assert type_symlink is not None
        assert latest_symlink is not None
        assert type_symlink.resolve() == test_file.resolve()

    def test_handles_file_outside_audio_dir(self, isolate_home_directory, tmp_path):
        """Test handling of files outside AUDIO_DIR (uses absolute path)."""
        from voice_mode.utils.symlinks import update_latest_symlinks
        from voice_mode.config import AUDIO_DIR

        AUDIO_DIR.mkdir(parents=True, exist_ok=True)

        # Create file outside AUDIO_DIR
        external_file = tmp_path / "external_audio.wav"
        external_file.write_bytes(b"external data")

        type_symlink, latest_symlink = update_latest_symlinks(external_file, "stt")

        # Should still create symlinks
        assert type_symlink is not None
        assert latest_symlink is not None

        # Symlink should use absolute path since file is outside AUDIO_DIR
        stt_target = os.readlink(type_symlink)
        assert os.path.isabs(stt_target) or str(external_file) in stt_target


class TestRemoveOldSymlinksFunction:
    """Tests specifically for _remove_old_symlinks helper."""

    def test_handles_empty_directory(self, isolate_home_directory):
        """Test that cleanup handles empty directory without errors."""
        from voice_mode.utils.symlinks import _remove_old_symlinks
        from voice_mode.config import AUDIO_DIR

        AUDIO_DIR.mkdir(parents=True, exist_ok=True)

        # Should not raise any errors
        _remove_old_symlinks(AUDIO_DIR, "latest")
        _remove_old_symlinks(AUDIO_DIR, "latest-STT")
        _remove_old_symlinks(AUDIO_DIR, "latest-TTS")

    def test_handles_nonexistent_directory(self, isolate_home_directory, tmp_path):
        """Test that cleanup handles nonexistent directory without errors."""
        from voice_mode.utils.symlinks import _remove_old_symlinks

        nonexistent_dir = tmp_path / "nonexistent"

        # Should not raise any errors (glob returns empty for nonexistent paths)
        _remove_old_symlinks(nonexistent_dir, "latest")

    def test_removes_multiple_matching_symlinks(self, isolate_home_directory):
        """Test that all matching symlinks are removed."""
        from voice_mode.utils.symlinks import _remove_old_symlinks
        from voice_mode.config import AUDIO_DIR

        AUDIO_DIR.mkdir(parents=True, exist_ok=True)

        # Create target
        target = AUDIO_DIR / "target.wav"
        target.write_bytes(b"target")

        # Create multiple symlinks matching pattern
        symlinks = [
            AUDIO_DIR / "latest.wav",
            AUDIO_DIR / "latest.mp3",
            AUDIO_DIR / "latest.flac",
        ]
        for s in symlinks:
            s.symlink_to(target)

        # Run cleanup
        _remove_old_symlinks(AUDIO_DIR, "latest")

        # All should be removed
        for s in symlinks:
            assert not s.exists(), f"{s.name} should be removed"
