"""Tests for one-time builtin starter-content seeding into DLC."""

from __future__ import annotations

import importlib
import sys

import builtin_content
import pytest


@pytest.fixture()
def server_mod(tmp_path, monkeypatch, isolate_logging):
    monkeypatch.setenv("CONFIG_DIR", str(tmp_path / "config"))
    (tmp_path / "config").mkdir()
    monkeypatch.delenv("DLC_DIR", raising=False)
    sys.modules.pop("server", None)
    mod = importlib.import_module("server")
    yield mod


def _source(server_mod):
    return (
        server_mod._feedBack_server_root()
        / builtin_content.BUILTIN_STARTER_SOURCES[0][1]
    )


def _dest(server_mod, dlc):
    return (
        dlc
        / builtin_content.BUILTIN_STARTER_SUBDIR
        / builtin_content.BUILTIN_STARTER_SOURCES[0][0]
    )


def test_seed_creates_starter_content_and_marker(tmp_path, server_mod):
    """First run copies the bundled feedpak into starter/ and writes the marker."""
    dlc = tmp_path / "dlc"
    dlc.mkdir()
    source = _source(server_mod)
    if not source.is_file():
        pytest.skip(f"starter source not present in checkout: {source}")

    builtin_content.seed_builtin_starter_content(server_mod._feedBack_server_root(), dlc)

    dest = _dest(server_mod, dlc)
    assert dest.is_file()
    assert dest.stat().st_size == source.stat().st_size
    assert (server_mod.CONFIG_DIR / builtin_content.STARTER_SEED_MARKER).is_file()


def test_seed_preserves_source_mtime(tmp_path, server_mod):
    """The seeded pack keeps the bundle's mtime so the diagnostic refresh check
    (source newer than dest -> update) stays correct across both write paths."""
    dlc = tmp_path / "dlc"
    dlc.mkdir()
    source = _source(server_mod)
    if not source.is_file():
        pytest.skip(f"starter source not present in checkout: {source}")

    builtin_content.seed_builtin_starter_content(server_mod._feedBack_server_root(), dlc)

    assert _dest(server_mod, dlc).stat().st_mtime_ns == source.stat().st_mtime_ns


def test_starter_is_not_carved_out_of_the_library():
    """`starter/` must NOT collide with the diagnostics/tutorials carve-out —
    otherwise seeded songs would never appear in the library listing."""
    assert "starter" not in {"diagnostics-builtin", "tutorials-builtin"}


def test_seed_runs_only_once_and_respects_deletion(tmp_path, server_mod):
    """After the first seed, deleting the song does NOT bring it back: the
    marker makes starter seeding a one-time welcome."""
    dlc = tmp_path / "dlc"
    dlc.mkdir()
    source = _source(server_mod)
    if not source.is_file():
        pytest.skip(f"starter source not present in checkout: {source}")

    builtin_content.seed_builtin_starter_content(server_mod._feedBack_server_root(), dlc)
    dest = _dest(server_mod, dlc)
    assert dest.is_file()

    # User removes the starter song.
    dest.unlink()

    # A subsequent launch must not re-seed it.
    builtin_content.seed_builtin_starter_content(server_mod._feedBack_server_root(), dlc)
    assert not dest.exists()


def test_seed_deferred_until_dlc_configured(tmp_path, server_mod):
    """With no DLC folder, seeding is skipped WITHOUT writing the marker, so it
    retries once a library folder exists."""
    source = _source(server_mod)
    if not source.is_file():
        pytest.skip(f"starter source not present in checkout: {source}")

    # dlc is None and DLC_DIR unset -> _get_dlc_dir() returns None.
    builtin_content.seed_builtin_starter_content(server_mod._feedBack_server_root(), None)
    assert not (server_mod.CONFIG_DIR / builtin_content.STARTER_SEED_MARKER).exists()

    # Now a DLC is configured: the deferred seed runs.
    dlc = tmp_path / "dlc"
    dlc.mkdir()
    builtin_content.seed_builtin_starter_content(server_mod._feedBack_server_root(), dlc)
    assert _dest(server_mod, dlc).is_file()
    assert (server_mod.CONFIG_DIR / builtin_content.STARTER_SEED_MARKER).is_file()


def test_seed_refuses_symlinked_seed_directory(tmp_path, server_mod):
    """A symlinked starter/ dir is refused so copies can't escape the DLC tree."""
    dlc = tmp_path / "dlc"
    dlc.mkdir()
    source = _source(server_mod)
    if not source.is_file():
        pytest.skip(f"starter source not present in checkout: {source}")

    outside_dir = tmp_path / "outside"
    outside_dir.mkdir()
    (dlc / builtin_content.BUILTIN_STARTER_SUBDIR).symlink_to(outside_dir)

    builtin_content.seed_builtin_starter_content(server_mod._feedBack_server_root(), dlc)

    assert list(outside_dir.iterdir()) == []
    # An incomplete seed must NOT write the marker, so a later launch retries.
    assert not (server_mod.CONFIG_DIR / builtin_content.STARTER_SEED_MARKER).exists()


def test_seed_never_overwrites_an_existing_user_file(tmp_path, server_mod):
    """One-time starter seeding must never replace a user's own file at the
    destination, even if the bundled pack has a newer mtime."""
    import os as _os

    dlc = tmp_path / "dlc"
    dlc.mkdir()
    dest = _dest(server_mod, dlc)
    dest.parent.mkdir(parents=True, exist_ok=True)
    dest.write_bytes(b"user's own edited pack")
    _os.utime(dest, (1_000_000, 1_000_000))  # far older than the bundled source

    builtin_content.seed_builtin_starter_content(server_mod._feedBack_server_root(), dlc)

    assert dest.read_bytes() == b"user's own edited pack"  # untouched
    # counted as already-present, so the one-time seed considers itself done
    assert (server_mod.CONFIG_DIR / builtin_content.STARTER_SEED_MARKER).is_file()


def test_seed_does_not_mark_when_destination_is_a_directory(tmp_path, server_mod):
    """A directory sitting at the destination name is neither clobbered nor
    counted as present, so the marker stays unwritten and seeding retries."""
    dlc = tmp_path / "dlc"
    dlc.mkdir()
    source = _source(server_mod)
    if not source.is_file():
        pytest.skip(f"starter source not present in checkout: {source}")

    bogus = _dest(server_mod, dlc)
    bogus.parent.mkdir(parents=True, exist_ok=True)
    bogus.mkdir()  # user (or junk) placed a directory where the pack goes

    builtin_content.seed_builtin_starter_content(server_mod._feedBack_server_root(), dlc)

    assert bogus.is_dir()  # untouched
    assert not (server_mod.CONFIG_DIR / builtin_content.STARTER_SEED_MARKER).exists()


def test_seed_does_not_mark_when_source_missing(tmp_path, server_mod, monkeypatch):
    """If a starter source can't be found, the marker stays unwritten and the
    seed is retried on the next launch (rather than permanently skipped)."""
    dlc = tmp_path / "dlc"
    dlc.mkdir()
    monkeypatch.setattr(
        builtin_content,
        "BUILTIN_STARTER_SOURCES",
        [("missing.feedpak", "content/starter/does-not-exist.feedpak")],
    )

    builtin_content.seed_builtin_starter_content(server_mod._feedBack_server_root(), dlc)

    assert not (dlc / builtin_content.BUILTIN_STARTER_SUBDIR / "missing.feedpak").exists()
    assert not (server_mod.CONFIG_DIR / builtin_content.STARTER_SEED_MARKER).exists()


def test_every_starter_source_file_is_present(server_mod):
    """Every entry in _BUILTIN_STARTER_SOURCES must have its bundled file on
    disk — otherwise the all-present gate never fires and NOTHING seeds (a
    listed-but-missing pack silently disables starter seeding entirely). In CI
    the checkout is clean, so "on disk" == committed."""
    root = server_mod._feedBack_server_root()
    missing = [
        rel for _, rel in builtin_content.BUILTIN_STARTER_SOURCES
        if not (root / rel).is_file()
    ]
    assert not missing, f"listed starter sources missing on disk: {missing}"


def test_seed_lands_every_listed_starter_pack(tmp_path, server_mod):
    """A real seed run copies every listed pack into starter/ and marks done."""
    root = server_mod._feedBack_server_root()
    for _, rel in builtin_content.BUILTIN_STARTER_SOURCES:
        if not (root / rel).is_file():
            pytest.skip(f"starter source not present in checkout: {rel}")

    dlc = tmp_path / "dlc"
    dlc.mkdir()
    builtin_content.seed_builtin_starter_content(server_mod._feedBack_server_root(), dlc)

    for dest_name, _ in builtin_content.BUILTIN_STARTER_SOURCES:
        dest = dlc / builtin_content.BUILTIN_STARTER_SUBDIR / dest_name
        assert dest.is_file(), f"pack not seeded: {dest_name}"
    assert (server_mod.CONFIG_DIR / builtin_content.STARTER_SEED_MARKER).is_file()


def test_no_unlisted_starter_pack_on_disk(server_mod):
    """The inverse guard: every content/starter/*.feedpak on disk must be wired
    into _BUILTIN_STARTER_SOURCES. An unlisted pack bundles into builds as dead
    weight and never seeds — exactly how the raw Ode-to-Joy pack slipped onto
    main before being wired up. In CI the checkout is clean, so this flags any
    stray/committed pack that isn't listed."""
    root = server_mod._feedBack_server_root()
    listed = {rel for _, rel in builtin_content.BUILTIN_STARTER_SOURCES}
    if not listed:
        pytest.skip("no starter sources declared")
    content_dir = (root / next(iter(listed))).parent  # all sources share this dir
    if not content_dir.is_dir():
        pytest.skip(f"starter content dir absent: {content_dir}")
    on_disk = {p.relative_to(root).as_posix() for p in content_dir.glob("*.feedpak")}
    unlisted = on_disk - listed
    assert not unlisted, (
        "committed but not in _BUILTIN_STARTER_SOURCES (would bundle as dead "
        f"weight and never seed): {sorted(unlisted)}"
    )
