"""Live integration tests for NFO creation and update using real TMDB data.

These tests call the real TMDB API and verify the complete NFO pipeline for
86: Eighty Six (TMDB 100565 / IMDB tt13718450 / TVDB 378609).

Run with:
    conda run -n AniWorld python -m pytest tests/integration/test_nfo_live_tmdb.py -v --tb=short
"""

import asyncio
from pathlib import Path

import pytest
from lxml import etree

from src.core.services.nfo_service import NFOService

# ---------------------------------------------------------------------------
# Show identity constants
# ---------------------------------------------------------------------------
TMDB_ID = 100565
IMDB_ID = "tt13718450"
TVDB_ID = 378609
SHOW_NAME = "86: Eighty Six"

# The API key is stored in data/config.json; import it via the settings system.
from src.config.settings import settings  # noqa: E402

TMDB_API_KEY: str = settings.tmdb_api_key or "299ae8f630a31bda814263c551361448"

# ---------------------------------------------------------------------------
# Required XML tags that must exist and be non-empty after creation/repair
# ---------------------------------------------------------------------------
REQUIRED_SINGLE_TAGS = [
    "title",
    "originaltitle",
    "sorttitle",
    "year",
    "plot",
    "outline",
    "runtime",
    "premiered",
    "status",
    "tmdbid",
    "imdbid",
    "tvdbid",
    "dateadded",
    "watched",
    # mpaa may be "TV-MA" (US) or an FSK value depending on config
    "mpaa",
]

REQUIRED_MULTI_TAGS = [
    "genre",
    "studio",
    "country",
]


# ---------------------------------------------------------------------------
# Helpers
# ---------------------------------------------------------------------------

def _parse_nfo(nfo_path: Path) -> etree._Element:
    """Parse NFO file and return root element."""
    tree = etree.parse(str(nfo_path))
    return tree.getroot()


def _assert_required_tags(root: etree._Element, nfo_path: Path) -> None:
    """Assert every required tag is present and non-empty."""
    missing = []
    for tag in REQUIRED_SINGLE_TAGS:
        elem = root.find(f".//{tag}")
        if elem is None or not (elem.text or "").strip():
            missing.append(tag)

    for tag in REQUIRED_MULTI_TAGS:
        elems = root.findall(f".//{tag}")
        if not elems or not any((e.text or "").strip() for e in elems):
            missing.append(tag)

    # At least one actor must be present
    actors = root.findall(".//actor/name")
    if not actors or not any((a.text or "").strip() for a in actors):
        missing.append("actor/name")

    assert not missing, (
        f"Missing or empty required tags in {nfo_path}:\n  "
        + "\n  ".join(missing)
        + f"\n\nFull NFO:\n{etree.tostring(root, pretty_print=True).decode()}"
    )


def _assert_correct_ids(root: etree._Element) -> None:
    """Assert that all three IDs have the expected values."""
    tmdbid = root.findtext(".//tmdbid")
    imdbid = root.findtext(".//imdbid")
    tvdbid = root.findtext(".//tvdbid")

    assert tmdbid == str(TMDB_ID), f"tmdbid: expected {TMDB_ID}, got {tmdbid!r}"
    assert imdbid == IMDB_ID, f"imdbid: expected {IMDB_ID!r}, got {imdbid!r}"
    assert tvdbid == str(TVDB_ID), f"tvdbid: expected {TVDB_ID}, got {tvdbid!r}"


# ---------------------------------------------------------------------------
# Fixtures
# ---------------------------------------------------------------------------

@pytest.fixture
def anime_dir(tmp_path: Path) -> Path:
    """Temporary anime root directory."""
    d = tmp_path / "anime"
    d.mkdir()
    return d


@pytest.fixture
def nfo_service(anime_dir: Path) -> NFOService:
    """NFOService pointing at the temp directory with the real API key."""
    return NFOService(
        tmdb_api_key=TMDB_API_KEY,
        anime_directory=str(anime_dir),
        image_size="w500",
        auto_create=True,
    )


# ---------------------------------------------------------------------------
# Test 1 – Create NFO and verify all required fields
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_create_nfo_has_all_required_fields(
    nfo_service: NFOService,
    anime_dir: Path,
) -> None:
    """Create a real tvshow.nfo via TMDB and assert every required tag is present.

    Uses 86: Eighty Six (TMDB 100565) as the reference show.
    All checks are performed against the TMDB API using the configured key.
    """
    series_folder = SHOW_NAME
    series_dir = anime_dir / series_folder
    series_dir.mkdir()

    # Patch image downloads to avoid network hits for images
    from unittest.mock import AsyncMock, patch

    with patch.object(
        nfo_service.image_downloader,
        "download_all_media",
        new_callable=AsyncMock,
        return_value={"poster": False, "logo": False, "fanart": False},
    ):
        nfo_path = await nfo_service.create_tvshow_nfo(
            serie_name=SHOW_NAME,
            serie_folder=series_folder,
            year=2021,
            download_poster=False,
            download_logo=False,
            download_fanart=False,
        )

    assert nfo_path.exists(), "NFO file was not created"

    root = _parse_nfo(nfo_path)

    # --- Structural checks ---
    _assert_required_tags(root, nfo_path)

    # --- Identity checks ---
    _assert_correct_ids(root)

    # --- Spot-check concrete values ---
    assert root.findtext(".//year") == "2021"
    assert root.findtext(".//premiered") == "2021-04-11"
    assert root.findtext(".//runtime") == "24"
    assert root.findtext(".//status") == "Ended"
    assert root.findtext(".//watched") == "false"

    # Plot must be non-trivial (at least 20 characters)
    plot = root.findtext(".//plot") or ""
    assert len(plot) >= 20, f"plot too short: {plot!r}"


# ---------------------------------------------------------------------------
# Test 2 – Strip NFO to ID-only, update, verify all fields restored
# ---------------------------------------------------------------------------

@pytest.mark.asyncio
async def test_update_stripped_nfo_restores_all_fields(
    nfo_service: NFOService,
    anime_dir: Path,
) -> None:
    """Write a minimal NFO with only the TMDB ID, run update_tvshow_nfo, and
    verify that all required tags are present with correct values afterwards.

    This proves the repair pipeline works end-to-end with a real TMDB lookup.
    """
    series_folder = SHOW_NAME
    series_dir = anime_dir / series_folder
    series_dir.mkdir()

    # Write the stripped NFO – only the tmdbid element, nothing else
    stripped_xml = (
        '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n'
        "<tvshow>\n"
        f"  <tmdbid>{TMDB_ID}</tmdbid>\n"
        "</tvshow>\n"
    )
    nfo_path = series_dir / "tvshow.nfo"
    nfo_path.write_text(stripped_xml, encoding="utf-8")

    # Confirm the file is truly incomplete before the update
    root_before = _parse_nfo(nfo_path)
    assert root_before.findtext(".//title") is None, "Precondition failed: title exists in stripped NFO"
    assert root_before.findtext(".//plot") is None, "Precondition failed: plot exists in stripped NFO"

    # Patch image downloads to avoid image network requests
    from unittest.mock import AsyncMock, patch

    with patch.object(
        nfo_service.image_downloader,
        "download_all_media",
        new_callable=AsyncMock,
        return_value={"poster": False, "logo": False, "fanart": False},
    ):
        updated_path = await nfo_service.update_tvshow_nfo(
            serie_folder=series_folder,
            download_media=False,
        )

    assert updated_path.exists(), "Updated NFO file not found"

    root_after = _parse_nfo(updated_path)

    # --- All required tags must now be present and non-empty ---
    _assert_required_tags(root_after, updated_path)

    # --- IDs must match ---
    _assert_correct_ids(root_after)

    # --- Concrete value checks ---
    assert root_after.findtext(".//year") == "2021"
    assert root_after.findtext(".//premiered") == "2021-04-11"
    assert root_after.findtext(".//runtime") == "24"
    assert root_after.findtext(".//status") == "Ended"
    assert root_after.findtext(".//watched") == "false"

    # Plot must be non-trivial
    plot = root_after.findtext(".//plot") or ""
    assert len(plot) >= 20, f"plot too short after update: {plot!r}"

    # Original title must be the Japanese title
    originaltitle = root_after.findtext(".//originaltitle") or ""
    assert originaltitle, "originaltitle is empty after update"
    # Should be the Japanese title (different from the English title)
    title = root_after.findtext(".//title") or ""
    assert originaltitle != "" and title != "", "title and originaltitle must both be set"

    # At least one genre
    genres = [e.text for e in root_after.findall(".//genre") if e.text]
    assert genres, "No genres found after update"

    # At least one studio
    studios = [e.text for e in root_after.findall(".//studio") if e.text]
    assert studios, "No studios found after update"

    # At least one actor with a name
    actor_names = [e.text for e in root_after.findall(".//actor/name") if e.text]
    assert actor_names, "No actors found after update"
