from unittest.mock import MagicMock, patch

import pytest

from lib.utils.general.utils import (
    TMDB_IMAGE_SIZES,
    build_media_metadata,
    extract_publish_date,
    extract_release_group,
    format_season_episode,
    get_image_size,
    get_random_color,
    get_rpdb_poster,
    info_hash_to_magnet,
    is_magnet_link,
    is_url,
    is_video,
    normalize_tv_data,
    set_listitem_artwork,
    supported_video_extensions,
    unicode_flag_to_country_code,
)


def test_is_url():
    assert is_url("http://example.com") is not None
    assert is_url("https://example.com/path?query=1") is not None
    assert is_url("ftp://example.com") is not None
    assert not is_url("not a url")
    assert not is_url("")


def test_is_magnet_link():
    magnet = "magnet:?xt=urn:btih:1234567890abcdef1234567890abcdef12345678"
    assert is_magnet_link(magnet) == magnet
    assert is_magnet_link("http://example.com") is None
    assert is_magnet_link("not a magnet") is None


def test_info_hash_to_magnet():
    info_hash = "1234567890abcdef1234567890abcdef12345678"
    magnet = info_hash_to_magnet(info_hash)
    assert info_hash in magnet
    assert magnet.startswith("magnet:?xt=urn:btih:")


def test_supported_video_extensions():
    exts = supported_video_extensions()
    assert isinstance(exts, (list, tuple))
    assert ".mp4" in exts
    assert ".mkv" in exts
    assert ".avi" in exts


def test_extract_publish_date():
    assert extract_publish_date("2023-10-27") == "2023-10-27"
    assert extract_publish_date("Released on 2022-01-01") == "2022-01-01"
    assert extract_publish_date("NoDate") == ""


def test_extract_release_group():
    assert extract_release_group("Movie.Title.2023.1080p-Groupname") == "Groupname"
    assert extract_release_group("[Groupname] Movie Title") == "Groupname"
    assert extract_release_group("Movie.Title.mkv") == ""
    assert extract_release_group(None) == ""


def test_unicode_flag_to_country_code():
    # US Flag 🇺🇸 -> us
    assert unicode_flag_to_country_code("🇺🇸") == "us"
    # GB Flag 🇬🇧 -> gb
    assert unicode_flag_to_country_code("🇬🇧") == "gb"
    # Invalid length
    assert unicode_flag_to_country_code("ABC") == "Invalid flag Unicode"


def test_is_video():
    assert is_video("movie.mp4") is True
    assert is_video("show.mkv") is True
    assert is_video("document.txt") is False
    assert is_video("archive.zip") is False


def test_get_random_color():
    color = get_random_color("Netflix")
    # Expected format: [B][COLOR FFRRGGBB]Netflix[/COLOR][/B]
    assert color.startswith("[B][COLOR FF")
    assert color.endswith("]Netflix[/COLOR][/B]")


@pytest.mark.parametrize(
    "season, episode, expected",
    [
        (1, 2, "S01E02"),
        ("1", "2", "S01E02"),
        ("01", "02", "S01E02"),
        ("special", "finale", "SspecialEfinale"),
        (None, "2", "E02"),
        ("3", None, "S03"),
        ("", "", ""),
    ],
)
def test_format_season_episode(season, episode, expected):
    assert format_season_episode(season, episode) == expected


def test_normalize_tv_data_decodes_episode_name_and_preserves_number_coercion():
    tv_data = {
        "name": "The%20Scales%20%26%20the%20Sword",
        "season": "2",
        "episode": "3",
    }

    normalized = normalize_tv_data(tv_data)

    assert normalized["name"] == "The Scales & the Sword"
    assert normalized["season"] == 2
    assert normalized["episode"] == 3


def test_normalize_tv_data_keeps_decoded_episode_name_stable():
    normalized = normalize_tv_data(
        {"name": "The Scales & the Sword", "season": 2, "episode": 3}
    )

    assert normalized["name"] == "The Scales & the Sword"
    assert normalized["season"] == 2
    assert normalized["episode"] == 3


class TestGetImageSize:
    @pytest.mark.parametrize(
        "setting_value, expected_sizes",
        [
            ("0", TMDB_IMAGE_SIZES["low"]),
            ("1", TMDB_IMAGE_SIZES["medium"]),
            ("2", TMDB_IMAGE_SIZES["high"]),
            ("3", TMDB_IMAGE_SIZES["original"]),
        ],
    )
    def test_maps_setting_to_tier(self, setting_value, expected_sizes):
        with patch("lib.utils.general.utils.get_setting_fresh", return_value=setting_value):
            for image_type in ("poster", "thumb", "profile", "fanart"):
                assert get_image_size(image_type) == expected_sizes[image_type]

    def test_invalid_setting_defaults_to_high(self):
        with patch("lib.utils.general.utils.get_setting_fresh", return_value="99"):
            assert get_image_size("poster") == TMDB_IMAGE_SIZES["high"]["poster"]
            assert get_image_size("fanart") == TMDB_IMAGE_SIZES["high"]["fanart"]

    def test_unknown_image_type_returns_empty_string(self):
        with patch("lib.utils.general.utils.get_setting_fresh", return_value="2"):
            assert get_image_size("unknown_type") == ""


class TestSetListitemArtwork:
    def _make_mock_item(self):
        item = MagicMock()
        item.setArt = MagicMock()
        return item

    def _extract_url(self, call_args, key):
        art_dict = call_args[0][0]
        return art_dict.get(key, "")

    @pytest.mark.parametrize(
        "setting_value, poster_size, fanart_size",
        [
            ("0", "w185", "w300"),
            ("1", "w342", "w780"),
            ("2", "w780", "w1280"),
            ("3", "original", "original"),
        ],
    )
    def test_uses_resolution_tier(self, setting_value, poster_size, fanart_size):
        item = self._make_mock_item()
        data = {
            "poster_path": "/poster.jpg",
            "backdrop_path": "/backdrop.jpg",
        }
        with patch("lib.utils.general.utils.get_setting_fresh", return_value=setting_value):
            set_listitem_artwork(item, data, {})

        assert item.setArt.called
        art_call = item.setArt.call_args
        assert poster_size in self._extract_url(art_call, "poster")
        assert fanart_size in self._extract_url(art_call, "fanart")
        assert poster_size in self._extract_url(art_call, "thumb")

    def test_falls_back_when_no_paths(self):
        item = self._make_mock_item()
        data = {}
        with patch("lib.utils.general.utils.get_setting_fresh", return_value="2"):
            set_listitem_artwork(item, data, {})

        assert item.setArt.called
        art_call = item.setArt.call_args
        assert self._extract_url(art_call, "poster") == ""
        assert self._extract_url(art_call, "fanart") == ""


class TestGetRpdbPoster:
    def test_cache_hit_returns_cached_value(self):
        cached_url = "https://cdn.ratingposterdb.com/poster.jpg"
        with patch("lib.utils.general.utils.cache") as mock_cache:
            mock_cache.get.return_value = cached_url
            result = get_rpdb_poster("tt1234567", "test_key")
            assert result == cached_url
            mock_cache.get.assert_called_once_with("rpdb_poster|tt1234567")
            mock_cache.set.assert_not_called()

    def test_cache_miss_success_with_poster_field(self):
        rpdb_url = "https://cdn.ratingposterdb.com/poster.jpg"
        response = MagicMock()
        response.status_code = 200
        response.json.return_value = {"poster": rpdb_url}

        with patch("lib.utils.general.utils.cache") as mock_cache:
            mock_cache.get.return_value = None
            with patch("requests.get", return_value=response) as mock_get:
                result = get_rpdb_poster("tt1234567", "test_key")
                assert result == rpdb_url
                mock_get.assert_called_once_with(
                    "https://api.ratingposterdb.com/test_key/imdb/tt1234567?lang=en",
                    timeout=10,
                )
                mock_cache.set.assert_called_once()
                call_args = mock_cache.set.call_args
                assert call_args[0][0] == "rpdb_poster|tt1234567"
                assert call_args[0][1] == rpdb_url

    def test_cache_miss_success_with_poster_large_field(self):
        rpdb_url = "https://cdn.ratingposterdb.com/poster_large.jpg"
        response = MagicMock()
        response.status_code = 200
        response.json.return_value = {"poster_large": rpdb_url}

        with patch("lib.utils.general.utils.cache") as mock_cache:
            mock_cache.get.return_value = None
            with patch("requests.get", return_value=response):
                result = get_rpdb_poster("tt1234567", "test_key")
                assert result == rpdb_url

    def test_cache_miss_api_error_returns_none(self):
        import requests as req_mod

        with patch("lib.utils.general.utils.cache") as mock_cache:
            mock_cache.get.return_value = None
            with patch(
                "requests.get",
                side_effect=req_mod.exceptions.RequestException("timeout"),
            ):
                result = get_rpdb_poster("tt1234567", "test_key")
                assert result is None
                mock_cache.set.assert_not_called()

    def test_cache_miss_non_200_status_returns_none(self):
        response = MagicMock()
        response.status_code = 403

        with patch("lib.utils.general.utils.cache") as mock_cache:
            mock_cache.get.return_value = None
            with patch("requests.get", return_value=response):
                result = get_rpdb_poster("tt1234567", "test_key")
                assert result is None
                mock_cache.set.assert_not_called()

    def test_cache_miss_empty_response_returns_none(self):
        response = MagicMock()
        response.status_code = 200
        response.json.return_value = {}

        with patch("lib.utils.general.utils.cache") as mock_cache:
            mock_cache.get.return_value = None
            with patch("requests.get", return_value=response):
                result = get_rpdb_poster("tt1234567", "test_key")
                assert result is None


class TestBuildMediaMetadataRpdb:
    @staticmethod
    def _make_tmdb_details(poster_path="/tmdb_poster.jpg"):
        details = MagicMock()
        details.poster_path = poster_path
        details.overview = "Overview"
        details.title = "Title"
        details.name = ""
        details.original_title = "Original Title"
        details.original_name = ""
        details.release_date = "2023-01-01"
        details.runtime = 120
        details.vote_average = 7.5
        details.vote_count = 100
        details.popularity = 50
        details.backdrop_path = "/backdrop.jpg"
        return details

    def test_rpdb_enabled_overrides_poster(self):
        ids = {"tmdb_id": "123", "imdb_id": "tt1234567"}
        rpdb_url = "https://cdn.ratingposterdb.com/poster.jpg"

        with patch(
            "lib.utils.general.utils.get_setting_fresh",
            side_effect=lambda key, default=None: {
                "rpdb_enabled": True,
                "rpdb_api_key": "test_key",
            }.get(key, default),
        ):
            with patch("lib.utils.general.utils.get_rpdb_poster", return_value=rpdb_url):
                with patch(
                    "lib.clients.tmdb.utils.utils.get_tmdb_media_details",
                    return_value=self._make_tmdb_details(),
                ):
                    with patch(
                        "lib.utils.general.utils.get_fanart_details",
                        return_value={},
                    ):
                        metadata = build_media_metadata(ids, "movies")
                        assert metadata["poster"] == rpdb_url

    def test_rpdb_enabled_fallback_to_tmdb_when_rpdb_none(self):
        ids = {"tmdb_id": "123", "imdb_id": "tt1234567"}

        with patch(
            "lib.utils.general.utils.get_setting_fresh",
            side_effect=lambda key, default=None: {
                "rpdb_enabled": True,
                "rpdb_api_key": "test_key",
            }.get(key, default),
        ):
            with patch("lib.utils.general.utils.get_rpdb_poster", return_value=None):
                with patch(
                    "lib.clients.tmdb.utils.utils.get_tmdb_media_details",
                    return_value=self._make_tmdb_details(),
                ):
                    with patch(
                        "lib.utils.general.utils.get_fanart_details",
                        return_value={},
                    ):
                        metadata = build_media_metadata(ids, "movies")
                        assert "w780" in metadata["poster"]

    def test_rpdb_disabled_uses_tmdb_poster(self):
        ids = {"tmdb_id": "123", "imdb_id": "tt1234567"}

        with patch(
            "lib.utils.general.utils.get_setting_fresh",
            side_effect=lambda key, default=None: {
                "rpdb_enabled": False,
                "rpdb_api_key": "",
            }.get(key, default),
        ):
            with patch("lib.utils.general.utils.get_rpdb_poster") as mock_get_rpdb:
                with patch(
                    "lib.clients.tmdb.utils.utils.get_tmdb_media_details",
                    return_value=self._make_tmdb_details(),
                ):
                    with patch(
                        "lib.utils.general.utils.get_fanart_details",
                        return_value={},
                    ):
                        metadata = build_media_metadata(ids, "movies")
                        mock_get_rpdb.assert_not_called()
                        assert "w780" in metadata["poster"]

    def test_rpdb_enabled_no_imdb_id_uses_tmdb_poster(self):
        ids = {"tmdb_id": "123"}

        with patch(
            "lib.utils.general.utils.get_setting_fresh",
            side_effect=lambda key, default=None: {
                "rpdb_enabled": True,
                "rpdb_api_key": "test_key",
            }.get(key, default),
        ):
            with patch("lib.utils.general.utils.get_rpdb_poster") as mock_get_rpdb:
                with patch(
                    "lib.clients.tmdb.utils.utils.get_tmdb_media_details",
                    return_value=self._make_tmdb_details(),
                ):
                    with patch(
                        "lib.utils.general.utils.get_fanart_details",
                        return_value={},
                    ):
                        metadata = build_media_metadata(ids, "movies")
                        mock_get_rpdb.assert_not_called()
                        assert "w780" in metadata["poster"]
