"""tests/regression/test_task_id_parser_v3.py — utils/task_id_parser v3 SSOT 확장 회귀 (task-2547).

task-2502 SSOT export 보존 + task-2547 verifier SSOT public alias (TASK_ID_STRICT_RE) 확장 검증.
"""
from __future__ import annotations

import importlib.util
import sys
from pathlib import Path

WORKSPACE = Path(__file__).resolve().parents[2]


def _load_module(mod_name: str, file_rel: str):
    file_path = WORKSPACE / file_rel
    spec = importlib.util.spec_from_file_location(mod_name, str(file_path))
    if spec is None or spec.loader is None:
        raise ImportError(f"cannot load spec for {file_path}")
    module = importlib.util.module_from_spec(spec)
    sys.modules[mod_name] = module
    spec.loader.exec_module(module)
    return module


tip = _load_module("tip_2547_v3", "utils/task_id_parser.py")


def test_v3_public_strict_alias_exposed():
    """TASK_ID_STRICT_RE 가 모듈 attribute 로 노출되어야 한다 (verifier SSOT)."""
    assert hasattr(tip, "TASK_ID_STRICT_RE"), "TASK_ID_STRICT_RE public alias 미노출"
    assert tip.TASK_ID_STRICT_RE is not None


def test_v3_strict_re_matches_word_boundary():
    """TASK_ID_STRICT_RE 가 단어 경계를 강제한다 (foo-task-2487-bar 비매치)."""
    m = tip.TASK_ID_STRICT_RE.search("foo-task-2487-bar")
    assert m is None, f"strict pattern boundary fail: matched {m.group(0) if m else None}"


def test_v3_strict_re_matches_clean_id():
    """TASK_ID_STRICT_RE 가 단어 단위 깨끗한 ID 는 잡는다."""
    m = tip.TASK_ID_STRICT_RE.search(" task-2487+1 ")
    assert m is not None
    assert m.group(0).strip() == "task-2487+1"


def test_v3_all_exports_present():
    """__all__ 에 핵심 심볼이 모두 포함되어 있어야 한다."""
    assert hasattr(tip, "__all__"), "__all__ 미정의"
    required = {
        "TASK_ID_V2_PATTERN",
        "TASK_ID_RE",
        "TASK_ID_PATTERN",
        "TASK_ID_STRICT_RE",
        "parse_task_id_v2",
        "is_valid_task_id",
        "is_valid_task_id_with_legacy",
        "extract_task_id",
        "extract_task_id_from_filename",
        "extract_task_id_from_branch",
    }
    missing = required - set(tip.__all__)
    assert not missing, f"__all__ 누락 심볼: {missing}"


def test_v3_task_2502_legacy_function_preserved():
    """task-2502 가 추가한 is_valid_task_id_with_legacy 가 보존되어 있어야 한다."""
    assert callable(tip.is_valid_task_id_with_legacy)
    assert tip.is_valid_task_id_with_legacy("task-1234.5") is True


def test_v3_task_2485_loose_boundary_preserved():
    """task-2485 boundary hardening (task-2472+10 잘림 방지) 가 보존되어야 한다."""
    out = tip.extract_task_id("foo task-2472+10 bar")
    assert out == "task-2472+10", f"loose boundary 회귀: {out}"


def test_v3_extract_task_id_from_filename():
    assert tip.extract_task_id_from_filename("memory/tasks/task-2487+1.md") == "task-2487+1"
    assert tip.extract_task_id_from_filename("task-2469_1.2_a+3.md") == "task-2469_1.2_a+3"


def test_v3_extract_task_id_from_branch():
    assert tip.extract_task_id_from_branch("task/task-2487+1-dev2") == "task-2487+1"
    assert tip.extract_task_id_from_branch("task/task-2469_1.2-dev3") == "task-2469_1.2"


def test_v3_parse_task_id_v2_includes_num_field():
    """task-2502 에서 추가된 num 필드가 보존되어야 한다."""
    out = tip.parse_task_id_v2("task-2487+1")
    assert out.get("num") == "2487"
    assert out.get("base") == "task-2487"
    assert out.get("retry") == "+1"
