"""Tests for the canonical naming module—R4 SHORT grammar (Phase 1 naming-reset).

Covers BUILD_SPEC Phase 1:
    - single-shot, strategy grouping, multi-shot pass, happy-accident suffix,
      sidecar compound ext
    - round-trip build/parse for every shape
    - parse_filename dual-reads legacy _PASS_ / strategy-token filenames
    - LEGACY_FILENAME_PATTERN still parses R3-era verbose names (lives in the
      migration script post-Phase-11)
    - bad take_suffix shape rejected by ValueError
"""

from __future__ import annotations

import pytest

from recoil.core.naming import (
    build_filename,
    next_take_number,
    parse_filename,
    parse_to_build_args,
)


def test_next_take_number_increments_no_overwrite(tmp_path):
    """REC-102 regression: re-generation must advance the take, never reuse
    (overwrite) an existing one. The scan stem is derived from build_filename,
    so it tracks the strategy-token grammar (the bug was a stale glob that no
    longer matched ``_COV_...`` names → repeat gen kept choosing take1)."""
    args = dict(
        episode=1,
        strategy="coverage",
        ordinal=7,
        shot_ids=["EP001_SH16", "EP001_SH17", "EP001_SH18"],
    )
    # empty dir → first take is 1
    assert next_take_number(tmp_path, **args) == 1
    # drop a REAL take1 file under the canonical strategy-token scheme
    take1 = tmp_path / build_filename(take=1, **args)
    take1.write_bytes(b"v1")
    assert take1.name == "EP001_COV_007_SH16_17_18_take1.mp4"
    # next must be 2 — returning 1 here is the overwrite bug
    assert next_take_number(tmp_path, **args) == 2
    # add take2 → next is 3 (max+1)
    (tmp_path / build_filename(take=2, **args)).write_bytes(b"v2")
    assert next_take_number(tmp_path, **args) == 3
    # the original take1 is never touched
    assert take1.exists() and take1.read_bytes() == b"v1"


def test_next_take_number_scoped_to_identity(tmp_path):
    """A different ordinal / shot set must NOT count toward this pass's takes."""
    a = dict(episode=1, strategy="coverage", ordinal=7,
             shot_ids=["EP001_SH16", "EP001_SH17"])
    b = dict(episode=1, strategy="coverage", ordinal=8,
             shot_ids=["EP001_SH20"])
    (tmp_path / build_filename(take=1, **b)).write_bytes(b"v")
    (tmp_path / build_filename(take=2, **b)).write_bytes(b"v")
    # pass A shares none of B's stem → starts fresh at 1
    assert next_take_number(tmp_path, **a) == 1
    # pass B correctly sees its own two takes → next is 3
    assert next_take_number(tmp_path, **b) == 3


def test_single_shot_short_grammar():
    name = build_filename(episode=1, shot_ids=["EP001_SH10"], take=16)
    assert name == "EP001_SH10_take16.mp4"


def test_multi_shot_pass_segment():
    name = build_filename(
        episode=1, pass_counter=7,
        shot_ids=["EP001_SH16", "EP001_SH17", "EP001_SH18"], take=2,
    )
    assert name == "EP001_PASS_007_SH16_17_18_take2.mp4"


@pytest.mark.parametrize(
    ("strategy", "ordinal", "token"),
    [
        ("continuity", 2, "CONT"),
        ("coverage", 11, "COV"),
        ("oner", 3, "ONER"),
    ],
)
def test_grouping_strategy_filename_builds_and_parses(strategy, ordinal, token):
    name = build_filename(
        episode=1,
        strategy=strategy,
        ordinal=ordinal,
        shot_ids=["EP001_SH16", "EP001_SH17", "EP001_SH18"],
        take=2,
    )

    assert name == f"EP001_{token}_{ordinal:03d}_SH16_17_18_take2.mp4"
    parsed = parse_filename(name)
    assert parsed is not None
    assert parsed["strategy"] == strategy
    assert parsed["ordinal"] == ordinal
    assert parsed["grouping_token"] == token
    assert parsed["legacy_grouping"] is False


def test_solo_strategy_is_tokenless_grouping_with_ordinal_zero():
    name = build_filename(
        episode=1,
        strategy="solo",
        ordinal=0,
        shot_ids=["EP001_SH10"],
        take=16,
    )

    assert name == "EP001_SH10_take16.mp4"
    parsed = parse_filename(name)
    assert parsed is not None
    assert parsed["strategy"] == "solo"
    assert parsed["ordinal"] == 0
    assert parsed["grouping_token"] is None
    assert parsed["pass_counter"] is None


def test_legacy_pass_parses_as_coverage_grouping_and_rebuilds_legacy():
    legacy_name = "EP001_PASS_017_SH33_33a_34_35_take1.mp4"

    parsed = parse_filename(legacy_name)
    assert parsed is not None
    assert parsed["strategy"] == "coverage"
    assert parsed["ordinal"] == 17
    assert parsed["pass_counter"] == 17
    assert parsed["grouping_token"] == "PASS"
    assert parsed["legacy_grouping"] is True

    build_args = parse_to_build_args(parsed)
    assert build_args["pass_counter"] == 17
    assert "strategy" not in build_args
    assert "ordinal" not in build_args
    assert build_filename(**build_args) == legacy_name


def test_strategy_build_rejects_mixed_legacy_pass_counter():
    with pytest.raises(ValueError):
        build_filename(
            episode=1,
            strategy="coverage",
            ordinal=1,
            pass_counter=1,
            shot_ids=["EP001_SH10"],
            take=1,
        )
    with pytest.raises(ValueError):
        build_filename(
            episode=1,
            ordinal=1,
            pass_counter=1,
            shot_ids=["EP001_SH10"],
            take=1,
        )


def test_happy_accident_suffix():
    name = build_filename(
        episode=1, shot_ids=["EP001_SH10"], take=16, take_suffix="~a",
    )
    assert name == "EP001_SH10_take16~a.mp4"


def test_sidecar_compound_ext():
    name = build_filename(
        episode=1, shot_ids=["EP001_SH33"], take=7, ext=".mp4.json",
    )
    assert name == "EP001_SH33_take7.mp4.json"


def test_round_trip_all_shapes():
    for args in [
        dict(episode=1, shot_ids=["EP001_SH10"], take=16),
        dict(episode=1, strategy="solo", ordinal=0,
             shot_ids=["EP001_SH10"], take=16),
        dict(episode=1, strategy="continuity", ordinal=2,
             shot_ids=["EP001_SH16", "EP001_SH17"], take=2),
        dict(episode=1, strategy="coverage", ordinal=11,
             shot_ids=["EP001_SH16", "EP001_SH17", "EP001_SH18"], take=2),
        dict(episode=1, strategy="oner", ordinal=3,
             shot_ids=["EP001_SH16", "EP001_SH17", "EP001_SH18"], take=2),
        dict(episode=1, shot_ids=["EP001_SH10"], take=16, take_suffix="~a"),
        dict(episode=1, pass_counter=7,
             shot_ids=["EP001_SH16", "EP001_SH17", "EP001_SH18"], take=2),
        dict(episode=1, shot_ids=["EP001_SH33"], take=7, ext=".mp4.json"),
        dict(episode=2, pass_counter=21,
             shot_ids=["EP002_SH1", "EP002_SH2a"], take=1),
    ]:
        name = build_filename(**args)
        parsed = parse_filename(name)
        assert parsed is not None, f"parse_filename(None) for {name!r}"
        rebuilt = build_filename(**parse_to_build_args(parsed))
        assert rebuilt == name, f"round-trip drift: {name!r} -> {rebuilt!r}"


def test_parse_returns_none_on_no_match():
    assert parse_filename("not_a_canonical_name.mp4") is None
    assert parse_filename(
        "TARTARUS_EP001_PASS_000_SH10_SOLO_ENV_seeddance-2-0_take16.mp4"
    ) is None  # legacy grammar rejected by new regex


def test_legacy_pattern_matches_old_grammar():
    """R3-era verbose names still parse under the legacy regex.

    The migration script (recoil/pipeline/tools/migrate_pass_names_r4.py) that
    once exported LEGACY_FILENAME_PATTERN was deleted in commit 8395db8d once the
    v2->v3 migration completed. The pattern is inlined here so legacy-name
    coverage survives the script's retirement.
    """
    import re as _re

    LEGACY_FILENAME_PATTERN = _re.compile(
        r"^([A-Z][A-Z0-9_]*?)"                                    # 1: project
        r"_(EP\d{3})"                                             # 2: episode
        r"_PASS_(\d{3})"                                          # 3: pass counter
        r"_SH(\d{1,4}[a-z]?(?:_\d{1,4}[a-z]?)*)"                  # 4: shot list
        r"_([A-Z][A-Z0-9]*(?:_[A-Z][A-Z0-9]*)*)"                 # 5: semantic tag
        r"_([a-z][a-z0-9\-]*)"                                    # 6: model filename_id
        r"_take(\d+)"                                             # 7: take
        r"(\.[a-zA-Z0-9]+(?:\.[a-zA-Z0-9]+)?)$"                  # 8: extension
    )
    m = LEGACY_FILENAME_PATTERN.match(
        "TARTARUS_EP001_PASS_000_SH10_SOLO_ENV_seeddance-2-0_take16.mp4"
    )
    assert m is not None
    assert m.group(1) == "TARTARUS"
    assert m.group(6) == "seeddance-2-0"


def test_bad_take_suffix_rejected():
    with pytest.raises(ValueError):
        build_filename(episode=1, shot_ids=["EP001_SH10"], take=1, take_suffix="~AB")
    with pytest.raises(ValueError):
        build_filename(episode=1, shot_ids=["EP001_SH10"], take=1, take_suffix="a")
