from types import SimpleNamespace
from unittest.mock import patch

import pytest
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

from app import schemas
from app.chain import mediaserver as MEDIA_SERVER_CHAIN_MODULE
from app.chain.mediaserver import MediaServerChain
from app.db import Base
from app.db.mediaserver_oper import MediaServerOper
from app.db.models.mediaserver import MediaServerItem


@pytest.fixture
def database(tmp_path):
    """创建隔离的媒体服务器测试数据库。"""
    engine = create_engine(f"sqlite:///{tmp_path / 'mediaserver.db'}")
    session_factory = sessionmaker(bind=engine)
    Base.metadata.create_all(bind=engine)
    yield session_factory
    engine.dispose()


def test_add_allows_same_item_id_across_servers(database):
    """不同媒体服务器允许复用相同 item_id。"""
    with database() as db:
        oper = MediaServerOper(db)

        assert oper.add(
            server="plex",
            library="movies",
            item_id="same-item-id",
            item_type="电影",
            title="Movie A",
        )
        assert oper.add(
            server="jellyfin",
            library="movies",
            item_id="same-item-id",
            item_type="电影",
            title="Movie B",
        )

        items = (
            db.query(MediaServerItem)
            .order_by(MediaServerItem.server.asc())
            .all()
        )

    assert len(items) == 2
    assert [item.server for item in items] == ["jellyfin", "plex"]


def test_media_count_reuses_existing_server_statistics():
    """整服同步应复用现有媒体统计并排除剧集集数。"""
    chain = object.__new__(MediaServerChain)
    chain.run_module = lambda *_args, **_kwargs: [
        schemas.Statistic(movie_count=12, tv_count=8, episode_count=200)
    ]

    assert chain.media_count("plex") == 20


def test_sync_updates_rows_and_removes_stale_entries(database):
    """同步应更新已存在条目，并清理未再出现或已移除服务的数据。"""
    old_sync_time = "2026-05-01 00:00:00"

    with database() as db:
        db.add_all(
            [
                MediaServerItem(
                    server="plex",
                    library="movies",
                    item_id="/library/metadata/1",
                    item_type="电影",
                    title="Old Title",
                    year="2024",
                    path="/media/old.mkv",
                    lst_mod_date=old_sync_time,
                ),
                MediaServerItem(
                    server="plex",
                    library="movies",
                    item_id="/library/metadata/2",
                    item_type="电影",
                    title="Stale Title",
                    year="2020",
                    path="/media/stale.mkv",
                    lst_mod_date=old_sync_time,
                ),
                MediaServerItem(
                    server="jellyfin",
                    library="movies",
                    item_id="/library/metadata/1",
                    item_type="电影",
                    title="Removed Server Title",
                    year="2024",
                    path="/media/removed.mkv",
                    lst_mod_date=old_sync_time,
                ),
            ]
        )
        db.commit()
        existing_id = (
            db.query(MediaServerItem.id)
            .filter(
                MediaServerItem.server == "plex",
                MediaServerItem.item_id == "/library/metadata/1",
            )
            .scalar()
        )

    chain = object.__new__(MediaServerChain)
    chain.librarys = lambda _server: [
        SimpleNamespace(id="movies", name="电影库"),
        SimpleNamespace(id="shows", name="剧集库"),
    ]
    chain.media_count = lambda _server: pytest.fail("部分媒体库同步不应使用整服统计")
    chain.items_count = lambda **_kwargs: 1
    chain.items = lambda **_kwargs: iter(
        [
            schemas.MediaServerItem(
                server="plex",
                library="movies",
                item_id="/library/metadata/1",
                item_type="Movie",
                title="New Title",
                year="2024",
                tmdbid=100,
                path="/media/new.mkv",
            )
        ]
    )
    chain.episodes = lambda *_args, **_kwargs: []

    with patch("app.db.ScopedSession", database), patch.object(
        MEDIA_SERVER_CHAIN_MODULE.ServiceConfigHelper,
        "get_mediaserver_configs",
        return_value=[SimpleNamespace(name="plex", enabled=True, sync_libraries=["movies"])],
    ):
        chain.sync()

    with database() as db:
        items = (
            db.query(MediaServerItem)
            .order_by(MediaServerItem.server.asc(), MediaServerItem.item_id.asc())
            .all()
        )

    assert len(items) == 1
    assert items[0].id == existing_id
    assert items[0].server == "plex"
    assert items[0].item_id == "/library/metadata/1"
    assert items[0].item_type == "电影"
    assert items[0].title == "New Title"
    assert items[0].path == "/media/new.mkv"
    assert items[0].lst_mod_date != old_sync_time


def test_sync_queries_counts_before_items_and_reports_media_progress(database):
    """同步前应查询全部目标媒体库总数，并按媒体条目更新进度。"""
    chain = object.__new__(MediaServerChain)
    events = []
    progress_snapshots = []
    server_libraries = {
        "plex-a": [SimpleNamespace(id="movies", name="电影库")],
        "plex-b": [SimpleNamespace(id="shows", name="剧集库")],
    }
    library_items = {
        ("plex-a", "movies"): [
            schemas.MediaServerItem(
                server="plex-a",
                library="movies",
                item_id=f"movie-{index}",
                item_type="Movie",
                title=f"电影 {index}",
            )
            for index in range(2)
        ],
        ("plex-b", "shows"): [
            schemas.MediaServerItem(
                server="plex-b",
                library="shows",
                item_id="show-1",
                item_type="Movie",
                title="剧集 1",
            )
        ],
    }

    chain.librarys = lambda server: server_libraries[server]

    def media_count(server):
        """记录整服统计顺序并返回待同步媒体总数。"""
        events.append(f"count:{server}")
        return sum(
            len(items)
            for (item_server, _library_id), items in library_items.items()
            if item_server == server
        )

    def items(**kwargs):
        """记录同步顺序并返回媒体库条目。"""
        server = kwargs["server"]
        library_id = kwargs["library_id"]
        events.append(f"items:{server}:{library_id}")
        return iter(library_items[(server, library_id)])

    chain.media_count = media_count
    chain.items_count = lambda **_kwargs: pytest.fail("整服同步不应逐库重复计数")
    chain.items = items
    chain.episodes = lambda *_args, **_kwargs: []

    with patch("app.db.ScopedSession", database), patch.object(
        MEDIA_SERVER_CHAIN_MODULE.ServiceConfigHelper,
        "get_mediaserver_configs",
        return_value=[
            SimpleNamespace(name="plex-a", enabled=True, sync_libraries=["all"]),
            SimpleNamespace(name="plex-b", enabled=True, sync_libraries=["all"]),
        ],
    ):
        chain.sync(
            progress_callback=lambda **kwargs: progress_snapshots.append(kwargs)
        )

    assert events == [
        "count:plex-a",
        "count:plex-b",
        "items:plex-a:movies",
        "items:plex-b:shows",
    ]
    media_progress = [
        snapshot
        for snapshot in progress_snapshots
        if snapshot["text"].startswith(("正在同步 plex-a 媒体库", "正在同步 plex-b 媒体库"))
    ]
    assert [round(snapshot["value"], 2) for snapshot in media_progress] == [
        33.33,
        66.67,
        100.0,
    ]
    assert media_progress[0]["data"]["media_total"] == 3
    assert media_progress[1]["data"]["library_media_finished"] == 2
    assert media_progress[2]["data"]["current_library"] == "剧集库"
    assert media_progress[2]["data"]["media_total"] == 3
    assert media_progress[2]["data"]["media_finished"] == 3
    progress_values = [snapshot["value"] for snapshot in progress_snapshots]
    assert progress_values == sorted(progress_values)
