import uuid
from unittest.mock import MagicMock

import pytest

from media_manager.exceptions import NotFoundError
from media_manager.tv.schemas import Show, ShowId, SeasonId
from media_manager.tv.service import TvService
from media_manager.indexer.schemas import IndexerQueryResult, IndexerQueryResultId
from media_manager.metadataProvider.schemas import MetaDataProviderSearchResult


@pytest.fixture
def mock_tv_repository():
    return MagicMock()


@pytest.fixture
def mock_torrent_service():
    return MagicMock()


@pytest.fixture
def mock_indexer_service():
    return MagicMock()


@pytest.fixture
def tv_service(mock_tv_repository, mock_torrent_service, mock_indexer_service):
    return TvService(
        tv_repository=mock_tv_repository,
        torrent_service=mock_torrent_service,
        indexer_service=mock_indexer_service,
    )


def test_add_show(tv_service, mock_tv_repository, mock_torrent_service):
    external_id = 123
    # Now we pass a mock metadata provider object
    mock_metadata_provider = MagicMock()
    show_data = Show(
        id=ShowId(uuid.uuid4()),
        name="Test Show",
        overview="Test Overview",
        year=2022,
        external_id=external_id,
        metadata_provider="tmdb",
        seasons=[],
    )
    mock_metadata_provider.get_show_metadata.return_value = show_data
    mock_metadata_provider.download_show_poster_image.return_value = True
    mock_tv_repository.save_show.return_value = show_data

    result = tv_service.add_show(
        external_id=external_id, metadata_provider=mock_metadata_provider
    )

    mock_metadata_provider.get_show_metadata.assert_called_once_with(id=external_id)
    mock_tv_repository.save_show.assert_called_once_with(show=show_data)
    mock_metadata_provider.download_show_poster_image.assert_called_once_with(
        show=show_data
    )
    assert result == show_data


def test_add_show_with_invalid_metadata(
    tv_service, mock_tv_repository, mock_torrent_service
):
    external_id = 123
    mock_metadata_provider = MagicMock()
    # Simulate metadata provider returning None
    mock_metadata_provider.get_show_metadata.return_value = None
    mock_metadata_provider.download_show_poster_image.return_value = False
    mock_tv_repository.save_show.return_value = None
    result = tv_service.add_show(
        external_id=external_id, metadata_provider=mock_metadata_provider
    )
    mock_metadata_provider.get_show_metadata.assert_called_once_with(id=external_id)
    assert result is None


def test_check_if_show_exists_by_external_id(
    tv_service, mock_tv_repository, mock_torrent_service
):
    external_id = 123
    metadata_provider = "tmdb"
    mock_tv_repository.get_show_by_external_id.return_value = "show_obj"
    assert tv_service.check_if_show_exists(
        external_id=external_id, metadata_provider=metadata_provider
    )
    mock_tv_repository.get_show_by_external_id.assert_called_once_with(
        external_id=external_id, metadata_provider=metadata_provider
    )

    mock_tv_repository.get_show_by_external_id.side_effect = NotFoundError
    assert not tv_service.check_if_show_exists(
        external_id=external_id, metadata_provider=metadata_provider
    )


def test_check_if_show_exists_by_show_id(
    tv_service, mock_tv_repository, mock_torrent_service
):
    show_id = ShowId(uuid.uuid4())
    mock_tv_repository.get_show_by_id.return_value = "show_obj"
    assert tv_service.check_if_show_exists(show_id=show_id)
    mock_tv_repository.get_show_by_id.assert_called_once_with(show_id=show_id)

    mock_tv_repository.get_show_by_id.side_effect = NotFoundError
    assert not tv_service.check_if_show_exists(show_id=show_id)


def test_check_if_show_exists_with_invalid_uuid(
    tv_service, mock_tv_repository, mock_torrent_service
):
    # Simulate NotFoundError for a random UUID
    show_id = ShowId(uuid.uuid4())
    mock_tv_repository.get_show_by_id.side_effect = NotFoundError
    assert not tv_service.check_if_show_exists(show_id=show_id)


def test_check_if_show_exists_raises_value_error(tv_service, mock_torrent_service):
    with pytest.raises(ValueError):
        tv_service.check_if_show_exists()


def test_add_season_request(tv_service, mock_tv_repository, mock_torrent_service):
    season_request = MagicMock()
    mock_tv_repository.add_season_request.return_value = season_request
    result = tv_service.add_season_request(season_request)
    mock_tv_repository.add_season_request.assert_called_once_with(
        season_request=season_request
    )
    assert result == season_request


def test_get_season_request_by_id(tv_service, mock_tv_repository, mock_torrent_service):
    season_request_id = MagicMock()
    season_request = MagicMock()
    mock_tv_repository.get_season_request.return_value = season_request
    result = tv_service.get_season_request_by_id(season_request_id)
    mock_tv_repository.get_season_request.assert_called_once_with(
        season_request_id=season_request_id
    )
    assert result == season_request


def test_update_season_request(tv_service, mock_tv_repository, mock_torrent_service):
    season_request = MagicMock()
    mock_tv_repository.add_season_request.return_value = season_request
    result = tv_service.update_season_request(season_request)
    mock_tv_repository.delete_season_request.assert_called_once_with(
        season_request_id=season_request.id
    )
    mock_tv_repository.add_season_request.assert_called_once_with(
        season_request=season_request
    )
    assert result == season_request


def test_delete_season_request(tv_service, mock_tv_repository, mock_torrent_service):
    season_request_id = MagicMock()
    tv_service.delete_season_request(season_request_id)
    mock_tv_repository.delete_season_request.assert_called_once_with(
        season_request_id=season_request_id
    )


def test_get_all_shows(tv_service, mock_tv_repository, mock_torrent_service):
    shows = [MagicMock(), MagicMock()]
    mock_tv_repository.get_shows.return_value = shows
    result = tv_service.get_all_shows()
    mock_tv_repository.get_shows.assert_called_once()
    assert result == shows


def test_get_show_by_id(tv_service, mock_tv_repository, mock_torrent_service):
    show_id = MagicMock()
    show = MagicMock()
    mock_tv_repository.get_show_by_id.return_value = show
    result = tv_service.get_show_by_id(show_id)
    mock_tv_repository.get_show_by_id.assert_called_once_with(show_id=show_id)
    assert result == show


def test_get_show_by_id_not_found(tv_service, mock_tv_repository, mock_torrent_service):
    show_id = ShowId(uuid.uuid4())
    mock_tv_repository.get_show_by_id.side_effect = NotFoundError
    try:
        tv_service.get_show_by_id(show_id)
    except NotFoundError:
        assert True
    else:
        assert False


def test_get_show_by_external_id(tv_service, mock_tv_repository, mock_torrent_service):
    external_id = 123
    metadata_provider = "tmdb"
    show = MagicMock()
    mock_tv_repository.get_show_by_external_id.return_value = show
    result = tv_service.get_show_by_external_id(external_id, metadata_provider)
    mock_tv_repository.get_show_by_external_id.assert_called_once_with(
        external_id=external_id, metadata_provider=metadata_provider
    )
    assert result == show


def test_get_show_by_external_id_not_found(
    tv_service, mock_tv_repository, mock_torrent_service
):
    external_id = 123
    metadata_provider = "tmdb"
    mock_tv_repository.get_show_by_external_id.side_effect = NotFoundError
    try:
        tv_service.get_show_by_external_id(external_id, metadata_provider)
    except NotFoundError:
        assert True
    else:
        assert False


def test_get_season(tv_service, mock_tv_repository, mock_torrent_service):
    season_id = MagicMock()
    season = MagicMock()
    mock_tv_repository.get_season.return_value = season
    result = tv_service.get_season(season_id)
    mock_tv_repository.get_season.assert_called_once_with(season_id=season_id)
    assert result == season


def test_get_season_not_found(tv_service, mock_tv_repository, mock_torrent_service):
    season_id = SeasonId(uuid.uuid4())
    mock_tv_repository.get_season.side_effect = NotFoundError
    try:
        tv_service.get_season(season_id)
    except NotFoundError:
        assert True
    else:
        assert False


def test_get_all_season_requests(tv_service, mock_tv_repository, mock_torrent_service):
    requests = [MagicMock(), MagicMock()]
    mock_tv_repository.get_season_requests.return_value = requests
    result = tv_service.get_all_season_requests()
    mock_tv_repository.get_season_requests.assert_called_once()
    assert result == requests


def test_get_public_season_files_by_season_id_downloaded(
    monkeypatch, tv_service, mock_tv_repository, mock_torrent_service
):
    season_id = MagicMock()
    season_file = MagicMock()
    public_season_file = MagicMock()
    public_season_file.downloaded = False
    mock_tv_repository.get_season_files_by_season_id.return_value = [season_file]
    monkeypatch.setattr(
        "media_manager.tv.schemas.PublicSeasonFile.model_validate",
        lambda x: public_season_file,
    )
    monkeypatch.setattr(
        tv_service, "season_file_exists_on_file", lambda season_file: True
    )
    result = tv_service.get_public_season_files_by_season_id(season_id)
    assert result[0].downloaded is True


def test_get_public_season_files_by_season_id_not_downloaded(
    monkeypatch, tv_service, mock_tv_repository, mock_torrent_service
):
    season_id = MagicMock()
    season_file = MagicMock()
    public_season_file = MagicMock()
    public_season_file.downloaded = False
    mock_tv_repository.get_season_files_by_season_id.return_value = [season_file]
    monkeypatch.setattr(
        "media_manager.tv.schemas.PublicSeasonFile.model_validate",
        lambda x: public_season_file,
    )
    monkeypatch.setattr(
        tv_service, "season_file_exists_on_file", lambda season_file: False
    )
    result = tv_service.get_public_season_files_by_season_id(season_id)
    assert result[0].downloaded is False


def test_get_public_season_files_by_season_id_empty(
    tv_service, mock_tv_repository, mock_torrent_service
):
    season_id = SeasonId(uuid.uuid4())
    mock_tv_repository.get_season_files_by_season_id.return_value = []
    result = tv_service.get_public_season_files_by_season_id(season_id)
    assert result == []


def test_is_season_downloaded_true(
    monkeypatch, tv_service, mock_tv_repository, mock_torrent_service
):
    season_id = MagicMock()
    season_file = MagicMock()
    mock_tv_repository.get_season_files_by_season_id.return_value = [season_file]
    monkeypatch.setattr(
        tv_service, "season_file_exists_on_file", lambda season_file: True
    )
    assert tv_service.is_season_downloaded(season_id) is True


def test_is_season_downloaded_false(
    monkeypatch, tv_service, mock_tv_repository, mock_torrent_service
):
    season_id = MagicMock()
    season_file = MagicMock()
    mock_tv_repository.get_season_files_by_season_id.return_value = [season_file]
    monkeypatch.setattr(
        tv_service, "season_file_exists_on_file", lambda season_file: False
    )
    assert tv_service.is_season_downloaded(season_id) is False


def test_is_season_downloaded_with_no_files(
    tv_service, mock_tv_repository, mock_torrent_service
):
    season_id = SeasonId(uuid.uuid4())
    mock_tv_repository.get_season_files_by_season_id.return_value = []
    assert tv_service.is_season_downloaded(season_id) is False


def test_season_file_exists_on_file_none(monkeypatch, tv_service, mock_torrent_service):
    season_file = MagicMock()
    season_file.torrent_id = None
    assert tv_service.season_file_exists_on_file(season_file) is True


def test_season_file_exists_on_file_imported(
    monkeypatch, tv_service, mock_torrent_service
):
    season_file = MagicMock()
    season_file.torrent_id = "torrent_id"
    torrent_file = MagicMock(imported=True)
    # Patch the repository method on the torrent_service instance
    tv_service.torrent_service.torrent_repository.get_torrent_by_id = MagicMock(
        return_value=torrent_file
    )
    assert tv_service.season_file_exists_on_file(season_file) is True


def test_season_file_exists_on_file_not_imported(
    monkeypatch, tv_service, mock_torrent_service
):
    season_file = MagicMock()
    season_file.torrent_id = "torrent_id"
    torrent_file = MagicMock()
    torrent_file.torrent_id = "torrent_id"
    torrent_file.imported = False
    tv_service.torrent_service.get_torrent_by_id = MagicMock(return_value=torrent_file)
    assert tv_service.season_file_exists_on_file(season_file) is False


def test_season_file_exists_on_file_with_none_imported(
    monkeypatch, tv_service, mock_torrent_service
):
    class DummySeasonFile:
        def __init__(self):
            self.torrent_id = uuid.uuid4()

    dummy_file = DummySeasonFile()

    class DummyTorrent:
        imported = True

    tv_service.torrent_service.torrent_repository.get_torrent_by_id = MagicMock(
        return_value=DummyTorrent()
    )
    assert tv_service.season_file_exists_on_file(dummy_file) is True


def test_season_file_exists_on_file_with_none_not_imported(
    monkeypatch, tv_service, mock_torrent_service
):
    class DummySeasonFile:
        def __init__(self):
            self.torrent_id = uuid.uuid4()

    dummy_file = DummySeasonFile()

    class DummyTorrent:
        imported = False

    tv_service.torrent_service.get_torrent_by_id = MagicMock(
        return_value=DummyTorrent()
    )
    assert tv_service.season_file_exists_on_file(dummy_file) is False


def test_get_all_available_torrents_for_a_season_no_override(
    tv_service, mock_tv_repository, mock_torrent_service, mock_indexer_service
):
    show_id = ShowId(uuid.uuid4())
    season_number = 1
    show_name = "Test Show"
    mock_show = Show(
        id=show_id,
        name=show_name,
        overview="",
        year=2020,
        external_id=1,
        metadata_provider="tmdb",
        seasons=[],
    )
    mock_tv_repository.get_show_by_id.return_value = mock_show

    torrent1 = IndexerQueryResult(
        id=IndexerQueryResultId(uuid.uuid4()),
        title="Test Show 1080p S01",
        download_url="url1",
        seeders=10,
        flags=[],
        size=100,
    )
    torrent2 = IndexerQueryResult(
        id=IndexerQueryResultId(uuid.uuid4()),
        title="Test Show 720p S01",
        download_url="url2",
        seeders=5,
        flags=[],
        size=100,
    )
    torrent3 = IndexerQueryResult(
        id=IndexerQueryResultId(uuid.uuid4()),
        title="Test Show 720p S01",
        download_url="url3",
        seeders=20,
        flags=[],
        size=100,
    )
    torrent4 = IndexerQueryResult(
        id=IndexerQueryResultId(uuid.uuid4()),
        title="Test Show S01E02",
        download_url="url4",
        seeders=5,
        flags=[],
        size=100,
    )  # Episode
    torrent5 = IndexerQueryResult(
        id=IndexerQueryResultId(uuid.uuid4()),
        title="Test Show S02",
        download_url="url5",
        seeders=10,
        flags=[],
        size=100,
    )  # Different season

    mock_indexer_service.search.return_value = [
        torrent1,
        torrent2,
        torrent3,
        torrent4,
        torrent5,
    ]

    results = tv_service.get_all_available_torrents_for_a_season(
        season_number=season_number, show_id=show_id
    )

    mock_tv_repository.get_show_by_id.assert_called_once_with(show_id=show_id)
    mock_indexer_service.search.assert_called_once_with(
        query=f"{show_name} s{str(season_number).zfill(2)}"
    )
    assert len(results) == 3
    assert torrent1 in results
    assert torrent2 in results
    assert torrent3 in results
    assert torrent4 not in results  # Should be filtered out
    assert torrent5 not in results  # Should be filtered out
    assert results == sorted(
        [torrent1, torrent3, torrent2]
    )  # Test sorting according to seeders and quality


def test_get_all_available_torrents_for_a_season_with_override(
    tv_service, mock_tv_repository, mock_torrent_service, mock_indexer_service
):
    show_id = ShowId(uuid.uuid4())
    season_number = 1
    override_query = "Custom Query S01"
    mock_show = Show(
        id=show_id,
        name="Test Show",
        overview="",
        year=2020,
        external_id=1,
        metadata_provider="tmdb",
        seasons=[],
    )
    mock_tv_repository.get_show_by_id.return_value = mock_show

    torrent1 = IndexerQueryResult(
        id=IndexerQueryResultId(uuid.uuid4()),
        title="Custom Query S01E01",
        download_url="url1",
        seeders=10,
        flags=[],
        size=100,
        # Remove 'season' argument if not supported by IndexerQueryResult
    )
    mock_indexer_service.search.return_value = [torrent1]

    results = tv_service.get_all_available_torrents_for_a_season(
        season_number=season_number,
        show_id=show_id,
        search_query_override=override_query,
    )

    mock_indexer_service.search.assert_called_once_with(query=override_query)
    assert results == [torrent1]


def test_get_all_available_torrents_for_a_season_no_results(
    tv_service, mock_tv_repository, mock_torrent_service, mock_indexer_service
):
    show_id = ShowId(uuid.uuid4())
    season_number = 1
    mock_show = Show(
        id=show_id,
        name="Test Show",
        overview="",
        year=2020,
        external_id=1,
        metadata_provider="tmdb",
        seasons=[],
    )
    mock_tv_repository.get_show_by_id.return_value = mock_show

    mock_indexer_service.search.return_value = []

    results = tv_service.get_all_available_torrents_for_a_season(
        season_number=season_number, show_id=show_id
    )
    assert results == []


def test_search_for_show_no_existing(tv_service, mock_torrent_service):
    query = "Test Show"
    mock_metadata_provider = MagicMock()
    search_result_item = MetaDataProviderSearchResult(
        external_id=123,
        name="Test Show",
        year=2022,
        overview="Overview",
        metadata_provider="tmdb",
        added=False,
        poster_path=None,
    )
    mock_metadata_provider.search_show.return_value = [search_result_item]
    mock_metadata_provider.name = "tmdb"
    tv_service.check_if_show_exists = MagicMock(return_value=False)
    results = tv_service.search_for_show(
        query=query, metadata_provider=mock_metadata_provider
    )
    mock_metadata_provider.search_show.assert_called_once_with(query)
    assert len(results) == 1
    assert results[0] == search_result_item
    assert results[0].added is False


def test_search_for_show_with_existing(tv_service, mock_torrent_service):
    query = "Test Show"
    mock_metadata_provider = MagicMock()
    search_result_item = MetaDataProviderSearchResult(
        external_id=123,
        name="Test Show",
        year=2022,
        overview="Overview",
        metadata_provider="tmdb",
        added=False,
        poster_path=None,
    )
    mock_metadata_provider.search_show.return_value = [search_result_item]
    mock_metadata_provider.name = "tmdb"
    tv_service.check_if_show_exists = MagicMock(return_value=True)
    results = tv_service.search_for_show(
        query=query, metadata_provider=mock_metadata_provider
    )
    assert len(results) == 1
    assert results[0].added is True


def test_search_for_show_empty_results(tv_service, mock_torrent_service):
    query = "NonExistent Show"
    mock_metadata_provider = MagicMock()
    mock_metadata_provider.search_show.return_value = []
    tv_service.check_if_show_exists = MagicMock()
    results = tv_service.search_for_show(
        query=query, metadata_provider=mock_metadata_provider
    )
    assert results == []


def test_get_popular_shows_none_added(tv_service, mock_torrent_service):
    mock_metadata_provider = MagicMock()
    popular_show1 = MetaDataProviderSearchResult(
        external_id=123,
        name="Popular Show 1",
        year=2022,
        overview="Overview1",
        metadata_provider="tmdb",
        added=False,
        poster_path=None,
    )
    popular_show2 = MetaDataProviderSearchResult(
        external_id=456,
        name="Popular Show 2",
        year=2023,
        overview="Overview2",
        metadata_provider="tmdb",
        added=False,
        poster_path=None,
    )
    mock_metadata_provider.search_show.return_value = [popular_show1, popular_show2]
    mock_metadata_provider.name = "tmdb"
    tv_service.check_if_show_exists = MagicMock(return_value=False)
    results = tv_service.get_popular_shows(metadata_provider=mock_metadata_provider)
    assert len(results) == 2
    assert popular_show1 in results
    assert popular_show2 in results


def test_get_popular_shows_all_added(tv_service, mock_torrent_service):
    mock_metadata_provider = MagicMock()
    popular_show1 = MetaDataProviderSearchResult(
        external_id=123,
        name="Popular Show 1",
        year=2022,
        overview="Overview1",
        metadata_provider="tmdb",
        added=False,
        poster_path=None,
    )
    mock_metadata_provider.search_show.return_value = [popular_show1]
    mock_metadata_provider.name = "tmdb"
    tv_service.check_if_show_exists = MagicMock(return_value=True)
    results = tv_service.get_popular_shows(metadata_provider=mock_metadata_provider)
    assert results == []


def test_get_popular_shows_empty_from_provider(tv_service, mock_torrent_service):
    mock_metadata_provider = MagicMock()
    mock_metadata_provider.search_show.return_value = []
    tv_service.check_if_show_exists = MagicMock()
    results = tv_service.get_popular_shows(metadata_provider=mock_metadata_provider)
    assert results == []
