"""Unit tests for NFO tag creation — Task 0.

Verifies that ``tmdb_to_nfo_model`` populates every required NFO tag and
that ``generate_tvshow_nfo`` writes all of them to the XML output.
"""

from datetime import datetime
from typing import Any, Dict, Optional
from unittest.mock import patch

import pytest
from lxml import etree

from src.core.entities.nfo_models import TVShowNFO
from src.core.utils.nfo_generator import generate_tvshow_nfo
from src.core.utils.nfo_mapper import _extract_rating_by_country, tmdb_to_nfo_model

# ---------------------------------------------------------------------------
# Helpers / fixtures
# ---------------------------------------------------------------------------


def _fake_get_image_url(path: str, size: str) -> str:
    """Minimal stand-in for TMDBClient.get_image_url used in tests."""
    return f"https://image.tmdb.org/t/p/{size}{path}"


MINIMAL_TMDB: Dict[str, Any] = {
    "id": 12345,
    "name": "Test Show",
    "original_name": "テストショー",
    "overview": "A great overview.",
    "tagline": "The best tagline.",
    "first_air_date": "2023-04-01",
    "status": "Continuing",
    "episode_run_time": [24],
    "vote_average": 8.5,
    "vote_count": 200,
    "genres": [{"id": 1, "name": "Animation"}, {"id": 2, "name": "Action"}],
    "networks": [{"id": 10, "name": "AT-X"}],
    "origin_country": ["JP"],
    "production_countries": [],
    "external_ids": {"imdb_id": "tt1234567", "tvdb_id": 99999},
    "poster_path": "/poster.jpg",
    "backdrop_path": "/backdrop.jpg",
    "images": {"logos": [{"file_path": "/logo.png"}]},
    "credits": {
        "cast": [
            {
                "id": 1,
                "name": "Actor One",
                "character": "Hero",
                "profile_path": "/actor1.jpg",
            }
        ]
    },
}

CONTENT_RATINGS_DE_US: Dict[str, Any] = {
    "results": [
        {"iso_3166_1": "DE", "rating": "12"},
        {"iso_3166_1": "US", "rating": "TV-PG"},
    ]
}


@pytest.fixture()
def nfo_model() -> TVShowNFO:
    """Return a fully-populated TVShowNFO from MINIMAL_TMDB data."""
    return tmdb_to_nfo_model(MINIMAL_TMDB, CONTENT_RATINGS_DE_US, _fake_get_image_url)


# ---------------------------------------------------------------------------
# tmdb_to_nfo_model — field mapping tests
# ---------------------------------------------------------------------------


def test_tmdb_to_nfo_model_sets_originaltitle(nfo_model: TVShowNFO) -> None:
    assert nfo_model.originaltitle == "テストショー"


def test_tmdb_to_nfo_model_sets_year_from_first_air_date(nfo_model: TVShowNFO) -> None:
    assert nfo_model.year == 2023


def test_tmdb_to_nfo_model_sets_plot_from_overview(nfo_model: TVShowNFO) -> None:
    assert nfo_model.plot == "A great overview."


def test_tmdb_to_nfo_model_sets_runtime(nfo_model: TVShowNFO) -> None:
    assert nfo_model.runtime == 24


def test_tmdb_to_nfo_model_sets_premiered(nfo_model: TVShowNFO) -> None:
    assert nfo_model.premiered == "2023-04-01"


def test_tmdb_to_nfo_model_sets_status(nfo_model: TVShowNFO) -> None:
    assert nfo_model.status == "Continuing"


def test_tmdb_to_nfo_model_sets_imdbid(nfo_model: TVShowNFO) -> None:
    assert nfo_model.imdbid == "tt1234567"


def test_tmdb_to_nfo_model_sets_genres(nfo_model: TVShowNFO) -> None:
    assert "Animation" in nfo_model.genre
    assert "Action" in nfo_model.genre


def test_tmdb_to_nfo_model_sets_studios_from_networks(nfo_model: TVShowNFO) -> None:
    assert "AT-X" in nfo_model.studio


def test_tmdb_to_nfo_model_sets_country(nfo_model: TVShowNFO) -> None:
    assert "JP" in nfo_model.country


def test_tmdb_to_nfo_model_sets_actors(nfo_model: TVShowNFO) -> None:
    assert len(nfo_model.actors) == 1
    assert nfo_model.actors[0].name == "Actor One"
    assert nfo_model.actors[0].role == "Hero"


def test_tmdb_to_nfo_model_sets_watched_false(nfo_model: TVShowNFO) -> None:
    assert nfo_model.watched is False


def test_tmdb_to_nfo_model_sets_tagline(nfo_model: TVShowNFO) -> None:
    assert nfo_model.tagline == "The best tagline."


def test_tmdb_to_nfo_model_sets_outline_from_overview(nfo_model: TVShowNFO) -> None:
    assert nfo_model.outline == "A great overview."


def test_tmdb_to_nfo_model_sets_sorttitle_from_name(nfo_model: TVShowNFO) -> None:
    assert nfo_model.sorttitle == "Test Show"


def test_tmdb_to_nfo_model_sets_dateadded(nfo_model: TVShowNFO) -> None:
    assert nfo_model.dateadded is not None
    # Must match YYYY-MM-DD HH:MM:SS
    datetime.strptime(nfo_model.dateadded, "%Y-%m-%d %H:%M:%S")


def test_tmdb_to_nfo_model_sets_mpaa_from_content_ratings(nfo_model: TVShowNFO) -> None:
    assert nfo_model.mpaa == "TV-PG"


# ---------------------------------------------------------------------------
# _extract_rating_by_country
# ---------------------------------------------------------------------------


def test_extract_rating_by_country_returns_us_rating() -> None:
    ratings = {"results": [{"iso_3166_1": "US", "rating": "TV-14"}]}
    assert _extract_rating_by_country(ratings, "US") == "TV-14"


def test_extract_rating_by_country_returns_none_when_no_match() -> None:
    ratings = {"results": [{"iso_3166_1": "DE", "rating": "12"}]}
    assert _extract_rating_by_country(ratings, "US") is None


def test_extract_rating_by_country_handles_empty_results() -> None:
    assert _extract_rating_by_country({"results": []}, "US") is None
    assert _extract_rating_by_country({}, "US") is None
    assert _extract_rating_by_country(None, "US") is None  # type: ignore[arg-type]


# ---------------------------------------------------------------------------
# generate_tvshow_nfo — XML output tests
# ---------------------------------------------------------------------------


def _parse_xml(xml_str: str) -> etree._Element:
    return etree.fromstring(xml_str.encode("utf-8"))


def test_generate_nfo_includes_all_required_tags(nfo_model: TVShowNFO) -> None:
    xml_str = generate_tvshow_nfo(nfo_model)
    root = _parse_xml(xml_str)

    required = [
        "title", "originaltitle", "year", "plot", "runtime",
        "premiered", "status", "imdbid", "genre", "studio",
        "country", "actor", "watched", "tagline", "outline",
        "sorttitle", "dateadded",
    ]
    for tag in required:
        elements = root.findall(f".//{tag}")
        assert elements, f"Missing required tag: <{tag}>"
        # At least one element must have non-empty text
        assert any(e.text for e in elements), f"Tag <{tag}> is empty"


def test_generate_nfo_writes_watched_false(nfo_model: TVShowNFO) -> None:
    xml_str = generate_tvshow_nfo(nfo_model)
    root = _parse_xml(xml_str)
    watched = root.find(".//watched")
    assert watched is not None
    assert watched.text == "false"


def test_generate_nfo_minimal_model_does_not_crash() -> None:
    minimal = TVShowNFO(title="Minimal Show")
    xml_str = generate_tvshow_nfo(minimal)
    assert "<title>Minimal Show</title>" in xml_str


def test_generate_nfo_writes_fsk_over_mpaa_when_prefer_fsk() -> None:
    nfo = TVShowNFO(title="Test", fsk="FSK 16", mpaa="TV-MA")
    with patch("src.core.utils.nfo_generator.settings") as mock_settings:
        mock_settings.nfo_prefer_fsk_rating = True
        xml_str = generate_tvshow_nfo(nfo)
    root = _parse_xml(xml_str)
    mpaa_elem = root.find(".//mpaa")
    assert mpaa_elem is not None
    assert mpaa_elem.text == "FSK 16"


def test_generate_nfo_writes_mpaa_when_no_fsk() -> None:
    nfo = TVShowNFO(title="Test", fsk=None, mpaa="TV-14")
    with patch("src.core.utils.nfo_generator.settings") as mock_settings:
        mock_settings.nfo_prefer_fsk_rating = True
        xml_str = generate_tvshow_nfo(nfo)
    root = _parse_xml(xml_str)
    mpaa_elem = root.find(".//mpaa")
    assert mpaa_elem is not None
    assert mpaa_elem.text == "TV-14"


# ---------------------------------------------------------------------------
# showtitle and namedseason — new coverage
# ---------------------------------------------------------------------------


def test_tmdb_to_nfo_model_sets_showtitle(nfo_model: TVShowNFO) -> None:
    """showtitle must equal the main title."""
    assert nfo_model.showtitle == "Test Show"


def test_generate_nfo_writes_showtitle(nfo_model: TVShowNFO) -> None:
    xml_str = generate_tvshow_nfo(nfo_model)
    root = _parse_xml(xml_str)
    elem = root.find(".//showtitle")
    assert elem is not None
    assert elem.text == "Test Show"


TMDB_WITH_SEASONS: Dict[str, Any] = {
    **MINIMAL_TMDB,
    "seasons": [
        {"season_number": 0, "name": "Specials"},
        {"season_number": 1, "name": "Season 1"},
        {"season_number": 2, "name": "Season 2"},
    ],
}


def test_tmdb_to_nfo_model_sets_namedseasons() -> None:
    model = tmdb_to_nfo_model(
        TMDB_WITH_SEASONS, CONTENT_RATINGS_DE_US, _fake_get_image_url,
    )
    assert len(model.namedseason) == 3
    assert model.namedseason[0].number == 0
    assert model.namedseason[0].name == "Specials"
    assert model.namedseason[1].number == 1


def test_generate_nfo_writes_namedseasons() -> None:
    model = tmdb_to_nfo_model(
        TMDB_WITH_SEASONS, CONTENT_RATINGS_DE_US, _fake_get_image_url,
    )
    xml_str = generate_tvshow_nfo(model)
    root = _parse_xml(xml_str)
    elems = root.findall(".//namedseason")
    assert len(elems) == 3
    assert elems[0].get("number") == "0"
    assert elems[0].text == "Specials"


def test_tmdb_to_nfo_model_no_seasons_key() -> None:
    """No 'seasons' key in TMDB data → namedseason list is empty."""
    model = tmdb_to_nfo_model(
        MINIMAL_TMDB, CONTENT_RATINGS_DE_US, _fake_get_image_url,
    )
    assert model.namedseason == []


def test_tmdb_to_nfo_model_empty_overview_produces_none_plot() -> None:
    """When overview is empty the plot field should be None."""
    data = {**MINIMAL_TMDB, "overview": ""}
    model = tmdb_to_nfo_model(
        data, CONTENT_RATINGS_DE_US, _fake_get_image_url,
    )
    assert model.plot is None


def test_generate_nfo_always_writes_plot_tag_even_when_none() -> None:
    """<plot> must always appear, even when plot is None."""
    nfo = TVShowNFO(title="No Plot Show")
    xml_str = generate_tvshow_nfo(nfo)
    root = _parse_xml(xml_str)
    plot_elem = root.find(".//plot")
    assert plot_elem is not None  # tag exists (always_write=True)
