# -*- coding: utf-8 -*-
"""tests.regression.test_bot_settings_policy_loader — task-2640 Track C.

회장 verbatim unfork #5 — bot_settings 정책 위치 + require_anu_callback_on_finish
=true 기본값 + fail-closed.

Layer A / NO-CRON: subprocess / cokacdir / merge / cron 호출 0.
"""
from __future__ import annotations

import importlib.util
import json
import sys
from pathlib import Path

import pytest

# task-2640 — utils 패키지가 tests/utils 등 그림자에 가려지지 않도록 sys.path
# 정리 후 worktree 의 실 utils.bot_settings_policy_loader 를 강제 로드.
_ROOT = Path(__file__).resolve().parents[2]
_TESTS_DIR = str(_ROOT / "tests")
sys.path[:] = [p for p in sys.path if p != _TESTS_DIR]
if str(_ROOT) not in sys.path:
    sys.path.insert(0, str(_ROOT))


def _load_real(modname: str, relpath: str):
    existing = sys.modules.get(modname)
    if existing is not None and getattr(existing, "__file__", "") and str(existing.__file__).endswith(relpath):
        return existing
    spec = importlib.util.spec_from_file_location(modname, _ROOT / relpath)
    assert spec is not None and spec.loader is not None, f"spec not found: {relpath}"
    mod = importlib.util.module_from_spec(spec)
    sys.modules[modname] = mod
    spec.loader.exec_module(mod)
    return mod


_load_real(
    "utils.bot_settings_policy_loader",
    "utils/bot_settings_policy_loader.py",
)

WORKSPACE = Path(__file__).resolve().parent.parent.parent
FIXTURE_ROOT = WORKSPACE / "tests" / "fixtures" / "self_collector_enforcement"


def _load_fixture(scenario: str) -> tuple[dict, dict]:
    fdir = FIXTURE_ROOT / scenario
    evidence = json.loads((fdir / "evidence.json").read_text(encoding="utf-8"))
    expected = json.loads((fdir / "expected.json").read_text(encoding="utf-8"))
    return evidence, expected


def test_default_policy_safe_defaults_verbatim() -> None:
    """DEFAULT_POLICY 가 spec §4.1 verbatim 안전 기본값을 보존한다."""
    from utils.bot_settings_policy_loader import DEFAULT_POLICY

    assert DEFAULT_POLICY["require_anu_callback_on_finish"] is True
    assert DEFAULT_POLICY["self_collector_forbidden"] is True
    assert DEFAULT_POLICY["sendfile_only_forbidden"] is True
    assert DEFAULT_POLICY["not_registered_forbidden"] is True
    assert DEFAULT_POLICY["fail_closed_on_violation"] is True
    assert DEFAULT_POLICY["anu_key_single_source"] == "c119085addb0f8b7"


def test_load_callback_policy_falls_back_to_default_when_candidates_missing(
    tmp_path: Path,
) -> None:
    """모든 후보 파일 부재 시 DEFAULT_POLICY 안전 적용 (fail-closed)."""
    from utils.bot_settings_policy_loader import (
        DEFAULT_POLICY,
        load_callback_policy,
    )

    evidence, expected = _load_fixture("bot_settings_default_safe_fallback")
    # tmp_path 하위 후보는 의도적으로 생성하지 않음 (fixture 정합).
    candidates = [tmp_path / Path(p).name for p in evidence["candidate_paths"]]
    result = load_callback_policy(candidate_paths=candidates)

    assert result.source == expected["source"]
    assert result.fell_back_to_default == expected["fell_back_to_default"]
    assert result.policy == expected["policy"]
    assert result.policy == DEFAULT_POLICY


def test_load_callback_policy_reads_workspace_config_when_present(
    tmp_path: Path,
) -> None:
    """후보 파일 존재 시 그 내용을 DEFAULT 위에 overlay 한다."""
    from utils.bot_settings_policy_loader import (
        DEFAULT_POLICY,
        load_callback_policy,
    )

    candidate = tmp_path / "callback_policy.json"
    candidate.write_text(
        json.dumps(
            {
                "callback_policy": {
                    "require_anu_callback_on_finish": True,
                    "anu_key_single_source": "c119085addb0f8b7",
                }
            }
        ),
        encoding="utf-8",
    )
    result = load_callback_policy(candidate_paths=[candidate])
    assert result.source == str(candidate)
    assert result.fell_back_to_default is False
    # missing keys filled with safe defaults.
    for k, v in DEFAULT_POLICY.items():
        assert result.policy[k] == v or k in (
            "require_anu_callback_on_finish",
            "anu_key_single_source",
        )


def test_check_finalize_policy_fail_closed_when_callback_missing() -> None:
    """callback 미등록 + sendfile_only=True → fail-closed 3종 분류."""
    from utils.bot_settings_policy_loader import check_finalize_policy

    evidence, expected = _load_fixture(
        "bot_finalize_require_callback_missing_fail_closed"
    )
    result = check_finalize_policy(
        evidence["finalize_state"], policy=evidence["policy"]
    )
    assert result.verdict == expected["verdict"]
    assert result.primary_classification == expected["primary_classification"]
    for cls in expected["must_contain_classifications"]:
        assert cls in result.classifications, (
            f"missing classification {cls!r} in {result.classifications}"
        )


def test_check_finalize_policy_pass_when_registered_with_schedule_id() -> None:
    """callback 정상 등록 + schedule_id 존재 + 정합 anu_key → PASS."""
    from utils.bot_settings_policy_loader import (
        DEFAULT_POLICY,
        check_finalize_policy,
    )

    result = check_finalize_policy(
        {
            "callback_registered": True,
            "schedule_id": "task-2640-finalize-pass-cron-001",
            "sendfile_only": False,
            "self_collector_attempted": False,
            "anu_key": "c119085addb0f8b7",
            "executor_key": "1e41a2324a3ccdd0",
            "task_id": "task-fixture-2640-finalize-pass",
        },
        policy=DEFAULT_POLICY,
    )
    assert result.verdict == "PASS"
    assert result.classifications == []


def test_check_finalize_policy_self_collector_attempt_blocked() -> None:
    """executor self-key 등록 시도 → SELF_COLLECTOR_FORBIDDEN_FINALIZE."""
    from utils.bot_settings_policy_loader import (
        DEFAULT_POLICY,
        check_finalize_policy,
    )

    result = check_finalize_policy(
        {
            "callback_registered": True,
            "schedule_id": "task-2640-finalize-self-key-cron-001",
            "sendfile_only": False,
            "self_collector_attempted": True,
            "anu_key": "1e41a2324a3ccdd0",
            "executor_key": "1e41a2324a3ccdd0",
            "task_id": "task-fixture-2640-self-collector",
        },
        policy=DEFAULT_POLICY,
    )
    assert result.verdict == "FAIL"
    assert "SELF_COLLECTOR_FORBIDDEN_FINALIZE" in result.classifications
    assert "ANU_KEY_MISMATCH_FINALIZE" in result.classifications


def test_workspace_callback_policy_json_exists_and_well_formed() -> None:
    """config/callback_policy.json 후보 파일이 존재하고 schema 정합."""
    candidate = WORKSPACE / "config" / "callback_policy.json"
    assert candidate.exists(), (
        "task-2640 Track C 후보 파일 부재 — config/callback_policy.json 필요"
    )
    obj = json.loads(candidate.read_text(encoding="utf-8"))
    assert "callback_policy" in obj
    cp = obj["callback_policy"]
    assert cp["require_anu_callback_on_finish"] is True
    assert cp["self_collector_forbidden"] is True
    assert cp["anu_key_single_source"] == "c119085addb0f8b7"
    assert cp["fail_closed_on_violation"] is True
