"""Tests for album filtering functionality."""

import unittest
import tempfile
import os
from pushtunes.utils.filters import AlbumFilter, FilterPattern
from pushtunes.models.album import Album


class TestFilterPattern(unittest.TestCase):
    """Test FilterPattern functionality."""

    def test_filter_pattern_creation(self):
        """Test creating filter patterns."""
        pattern = FilterPattern(
            field="artist",
            pattern=".*dead.*",
            regex=None,  # We'll test regex compilation separately
        )
        self.assertEqual(pattern.field, "artist")
        self.assertEqual(pattern.pattern, ".*dead.*")


class TestAlbumFilter(unittest.TestCase):
    """Test AlbumFilter functionality."""

    def setUp(self):
        """Set up test albums."""
        self.test_albums = [
            Album.by_single_artist(
                "Dead Can Dance", title="Into the Labyrinth", service_id="1"
            ),
            Album.by_single_artist(
                "Dance with the Dead", title="Send the Signal", service_id="2"
            ),
            Album.by_single_artist("Faith No More", title="Angel Dust", service_id="3"),
            Album.by_single_artist(
                "Metallica", title="Master of Puppets", service_id="4"
            ),
            Album.by_single_artist("The Beatles", title="Abbey Road", service_id="5"),
            Album.by_single_artist(
                "Pink Floyd", title="Dark Side of the Moon", service_id="6"
            ),
            Album.by_single_artist(
                "Dead Can Dance", title="The Serpent's Egg", service_id="7"
            ),
        ]

    def test_empty_filter(self):
        """Test empty filter includes all albums."""
        filter_obj = AlbumFilter()

        for album in self.test_albums:
            self.assertTrue(filter_obj.matches(album))

    def test_single_artist_pattern(self):
        """Test single artist pattern matching."""
        filter_obj = AlbumFilter.from_string("artist:'.*dead.*'")

        # Should match "Dead Can Dance" and "Dance with the Dead"
        expected_matches = ["1", "2", "7"]
        actual_matches = []

        for album in self.test_albums:
            if filter_obj.matches(album):
                actual_matches.append(album.service_id)

        self.assertEqual(set(expected_matches), set(actual_matches))

    def test_single_album_pattern(self):
        """Test single album pattern matching."""
        filter_obj = AlbumFilter.from_string("album:'.*signal.*'")

        # Should only match "Send the Signal"
        matches = []
        for album in self.test_albums:
            if filter_obj.matches(album):
                matches.append(album.service_id)

        self.assertEqual(matches, ["2"])

    def test_multiple_patterns_or_logic(self):
        """Test multiple patterns with OR logic."""
        filter_obj = AlbumFilter.from_string("artist:'.*dead.*',artist:'faith.*'")

        # Should match Dead Can Dance, Dance with the Dead, and Faith No More
        expected_matches = ["1", "2", "3", "7"]
        actual_matches = []

        for album in self.test_albums:
            if filter_obj.matches(album):
                actual_matches.append(album.service_id)

        self.assertEqual(set(expected_matches), set(actual_matches))

    def test_mixed_artist_album_patterns(self):
        """Test mixing artist and album patterns."""
        filter_obj = AlbumFilter.from_string("artist:'.*dead.*',album:'.*signal.*'")

        # Should match Dead Can Dance albums + "Send the Signal"
        expected_matches = ["1", "2", "7"]  # All dead albums + signal album
        actual_matches = []

        for album in self.test_albums:
            if filter_obj.matches(album):
                actual_matches.append(album.service_id)

        self.assertEqual(set(expected_matches), set(actual_matches))

    def test_case_insensitive_matching(self):
        """Test that pattern matching is case insensitive."""
        filter_obj = AlbumFilter.from_string("artist:'DEAD.*'")

        # Should still match "Dead Can Dance" despite case difference
        matches = []
        for album in self.test_albums:
            if filter_obj.matches(album):
                matches.append(album.service_id)

        self.assertIn("1", matches)  # Dead Can Dance
        self.assertIn("7", matches)  # Dead Can Dance

    def test_exact_match_pattern(self):
        """Test exact match patterns."""
        filter_obj = AlbumFilter.from_string("artist:'^Dead Can Dance$'")

        # Should only match "Dead Can Dance", not "Dance with the Dead"
        expected_matches = ["1", "7"]
        actual_matches = []

        for album in self.test_albums:
            if filter_obj.matches(album):
                actual_matches.append(album.service_id)

        self.assertEqual(set(expected_matches), set(actual_matches))


class TestFilterParsing(unittest.TestCase):
    """Test filter string parsing."""

    def test_basic_pattern_parsing(self):
        """Test parsing basic patterns."""
        filter_obj = AlbumFilter.from_string("artist:'test'")
        self.assertEqual(len(filter_obj.patterns), 1)
        self.assertEqual(filter_obj.patterns[0].field, "artist")
        self.assertEqual(filter_obj.patterns[0].pattern, "test")

    def test_multiple_pattern_parsing(self):
        """Test parsing multiple comma-separated patterns."""
        filter_obj = AlbumFilter.from_string(
            "artist:'test1',album:'test2',artist:'test3'"
        )
        self.assertEqual(len(filter_obj.patterns), 3)

        # Check all patterns were parsed
        fields = [p.field for p in filter_obj.patterns]
        patterns = [p.pattern for p in filter_obj.patterns]

        self.assertIn("artist", fields)
        self.assertIn("album", fields)
        self.assertIn("test1", patterns)
        self.assertIn("test2", patterns)
        self.assertIn("test3", patterns)

    def test_pattern_with_commas_in_regex(self):
        """Test patterns that contain commas within the regex."""
        filter_obj = AlbumFilter.from_string("artist:'test,with,commas'")
        self.assertEqual(len(filter_obj.patterns), 1)
        self.assertEqual(filter_obj.patterns[0].pattern, "test,with,commas")

    def test_invalid_pattern_format(self):
        """Test that invalid patterns raise errors."""
        with self.assertRaises(ValueError):
            AlbumFilter.from_string("invalid_pattern")

        with self.assertRaises(ValueError):
            AlbumFilter.from_string("artist:no_quotes")

        with self.assertRaises(ValueError):
            AlbumFilter.from_string("invalid_field:'test'")

    def test_invalid_regex_pattern(self):
        """Test that invalid regex patterns raise errors."""
        with self.assertRaises(ValueError):
            AlbumFilter.from_string("artist:'[invalid regex'")

    def test_empty_filter_string(self):
        """Test empty filter string creates empty filter."""
        filter_obj = AlbumFilter.from_string("")
        self.assertEqual(len(filter_obj.patterns), 0)

        filter_obj = AlbumFilter.from_string("   ")
        self.assertEqual(len(filter_obj.patterns), 0)


class TestFilterFromFile(unittest.TestCase):
    """Test loading filters from files."""

    def test_filter_from_file(self):
        """Test loading filter from file."""
        with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as f:
            f.write("artist:'.*dead.*'\n")
            f.write("album:'.*signal.*'\n")
            f.write("# This is a comment\n")
            f.write("\n")  # Empty line
            f.write("artist:'faith.*'\n")
            temp_file = f.name

        try:
            filter_obj = AlbumFilter.from_file(temp_file)
            self.assertEqual(len(filter_obj.patterns), 3)

            # Check patterns were loaded correctly
            patterns = [p.pattern for p in filter_obj.patterns]
            self.assertIn(".*dead.*", patterns)
            self.assertIn(".*signal.*", patterns)
            self.assertIn("faith.*", patterns)

        finally:
            os.unlink(temp_file)

    def test_filter_from_file_multiple_patterns_per_line(self):
        """Test loading multiple patterns per line from file."""
        with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as f:
            f.write("artist:'.*dead.*',album:'.*signal.*'\n")
            f.write("artist:'faith.*'\n")
            temp_file = f.name

        try:
            filter_obj = AlbumFilter.from_file(temp_file)
            self.assertEqual(len(filter_obj.patterns), 3)
        finally:
            os.unlink(temp_file)

    def test_filter_from_nonexistent_file(self):
        """Test loading from non-existent file raises error."""
        with self.assertRaises(FileNotFoundError):
            AlbumFilter.from_file("/nonexistent/file.txt")

    def test_filter_from_file_with_invalid_pattern(self):
        """Test that invalid patterns in file raise errors."""
        with tempfile.NamedTemporaryFile(mode="w", delete=False, suffix=".txt") as f:
            f.write("artist:'valid_pattern'\n")
            f.write("invalid_pattern_format\n")
            temp_file = f.name

        try:
            with self.assertRaises(ValueError):
                AlbumFilter.from_file(temp_file)
        finally:
            os.unlink(temp_file)


class TestFilterSummary(unittest.TestCase):
    """Test filter summary functionality."""

    def test_empty_filter_summary(self):
        """Test summary for empty filter."""
        filter_obj = AlbumFilter()
        summary = filter_obj.get_summary()
        self.assertEqual(summary, "No filters (all albums included)")

    def test_single_pattern_summary(self):
        """Test summary for single pattern."""
        filter_obj = AlbumFilter.from_string("artist:'.*dead.*'")
        summary = filter_obj.get_summary()
        self.assertIn("Artist patterns: .*dead.*", summary)

    def test_mixed_patterns_summary(self):
        """Test summary for mixed patterns."""
        filter_obj = AlbumFilter.from_string(
            "artist:'.*dead.*',album:'.*signal.*',artist:'faith.*'"
        )
        summary = filter_obj.get_summary()

        # Should contain both artist and album sections
        self.assertIn("Artist patterns:", summary)
        self.assertIn("Album patterns:", summary)
        self.assertIn(".*dead.*", summary)
        self.assertIn(".*signal.*", summary)
        self.assertIn("faith.*", summary)


class TestRealWorldExamples(unittest.TestCase):
    """Test with real-world filter examples."""

    def setUp(self):
        """Set up test data with real-world examples."""
        self.albums = [
            Album.by_single_artist(
                "Dead Can Dance", title="Into the Labyrinth", service_id="1"
            ),
            Album.by_single_artist(
                "Dance with the Dead", title="Send the Signal", service_id="2"
            ),
            Album.by_single_artist("Faith No More", title="Angel Dust", service_id="3"),
            Album.by_single_artist("The Faith", title="Some Album", service_id="4"),
            Album.by_single_artist(
                "Metallica", title="Master of Puppets", service_id="5"
            ),
            Album.by_single_artist(
                "deadmau5", title="Random Album Title", service_id="6"
            ),
        ]

    def test_user_example_1(self):
        filter_obj = AlbumFilter.from_string("artist:'.*dead.*',artist:'faith.*'")

        matches = []
        for album in self.albums:
            if filter_obj.matches(album):
                matches.append(f"{album.artist} - {album.title}")

        # Should include Dead Can Dance, Dance with the Dead, Faith No More, deadmau5
        expected_artists = [
            "Dead Can Dance",
            "Dance with the Dead",
            "Faith No More",
            "deadmau5",
        ]
        matched_artists = [
            album.artist for album in self.albums if filter_obj.matches(album)
        ]

        for expected in expected_artists:
            self.assertIn(expected, matched_artists)

    def test_user_example_2(self):
        filter_obj = AlbumFilter.from_string("artist:'.*dead.*',album:'.*signal.*'")

        matched_albums = []
        for album in self.albums:
            if filter_obj.matches(album):
                matched_albums.append(f"{album.artist} - {album.title}")

        # Should include Dead Can Dance albums, Dance with the Dead albums,
        # deadmau5 albums, AND "Send the Signal" by Dance with the Dead
        expected_matches = [
            "Dead Can Dance - Into the Labyrinth",
            "Dance with the Dead - Send the Signal",  # Matches both artist and album criteria
            "deadmau5 - Random Album Title",
        ]

        for expected in expected_matches:
            self.assertIn(expected, matched_albums)


if __name__ == "__main__":
    unittest.main()
