"""anu_v2.tests.test_owner_trigger_pat_phase0_2553 — Phase 0 secret 인프라 단위 테스트.

회장 §명시 Phase 0 (task-2553):
  - token env name `OWNER_GEMINI_TRIGGER_PAT` 상수 정의 확인
  - token loader fail-fast (token 누락 → 즉시 예외, default GH_TOKEN fallback 금지)
  - token redaction guard (`_redact_token` 외부 출력 경로 안전성)
  - audit 박제 시 token_present + token_hash 만, token raw 0

본 회귀는 anu_v2/* 모듈만 import 한다 (one-way isolation).
"""

from __future__ import annotations

import hashlib
import sys
from pathlib import Path

import pytest

# workspace root → sys.path (anu_v2 패키지를 절대 import 하기 위함)
WORKSPACE_ROOT = Path(__file__).resolve().parents[2]
if str(WORKSPACE_ROOT) not in sys.path:
    sys.path.insert(0, str(WORKSPACE_ROOT))

from anu_v2.owner_trigger_pat import (  # noqa: E402
    OWNER_PAT_ENV_NAME,
    REDACTED_PLACEHOLDER,
    _hash_token,
    _redact_token,
    load_owner_pat,
)


# ─── A. 상수 박제 ─────────────────────────────────────────────────────────────
def test_phase0_owner_pat_env_name_constant() -> None:
    """token env 이름은 정확히 `OWNER_GEMINI_TRIGGER_PAT` 상수로 박제."""
    assert OWNER_PAT_ENV_NAME == "OWNER_GEMINI_TRIGGER_PAT"


# ─── B. token loader fail-fast ────────────────────────────────────────────────
def test_phase0_load_owner_pat_returns_token_when_set(monkeypatch: pytest.MonkeyPatch) -> None:
    monkeypatch.setenv(OWNER_PAT_ENV_NAME, "github_pat_FAKE_OWNER_TOKEN")
    token = load_owner_pat()
    assert token == "github_pat_FAKE_OWNER_TOKEN"


def test_phase0_load_owner_pat_fail_fast_when_missing(monkeypatch: pytest.MonkeyPatch) -> None:
    """token 누락 시 RuntimeError — silent fallback / default 값 반환 금지."""
    monkeypatch.delenv(OWNER_PAT_ENV_NAME, raising=False)
    with pytest.raises(RuntimeError) as excinfo:
        load_owner_pat()
    # 에러 메시지에 env 이름은 노출되지만 token 값은 X (애초에 값이 없음).
    assert OWNER_PAT_ENV_NAME in str(excinfo.value)
    assert "OWNER_PAT_MISSING" in str(excinfo.value)


def test_phase0_load_owner_pat_fail_fast_when_empty_string(monkeypatch: pytest.MonkeyPatch) -> None:
    """빈 문자열 / 공백 only token 도 fail-fast (silent 통과 금지)."""
    monkeypatch.setenv(OWNER_PAT_ENV_NAME, "   ")
    with pytest.raises(RuntimeError):
        load_owner_pat()


def test_phase0_load_owner_pat_no_default_gh_token_fallback(monkeypatch: pytest.MonkeyPatch) -> None:
    """OWNER_GEMINI_TRIGGER_PAT 미설정 시, GH_TOKEN/GITHUB_TOKEN 이 환경에 있어도 fallback X.

    회장 §15 금지 14: default GH_TOKEN fallback 절대 금지.
    """
    monkeypatch.delenv(OWNER_PAT_ENV_NAME, raising=False)
    monkeypatch.setenv("GH_TOKEN", "ghp_BOT_TOKEN_LEAK_FALLBACK")
    monkeypatch.setenv("GITHUB_TOKEN", "ghp_BOT_TOKEN_LEAK_FALLBACK")
    with pytest.raises(RuntimeError):
        load_owner_pat()


def test_phase0_load_owner_pat_custom_env_name(monkeypatch: pytest.MonkeyPatch) -> None:
    """token_env 인자 override 가능 (테스트 격리용)."""
    monkeypatch.setenv("CUSTOM_TEST_PAT", "github_pat_TEST_OVERRIDE")
    token = load_owner_pat(token_env="CUSTOM_TEST_PAT")
    assert token == "github_pat_TEST_OVERRIDE"


# ─── C. token redaction guard ────────────────────────────────────────────────
def test_phase0_redact_token_replaces_substring() -> None:
    token = "github_pat_VERYSECRETTOKEN"
    text = f"error: HTTP 401 unauthorized for token {token} (PR #81)"
    redacted = _redact_token(text, token)
    assert token not in redacted
    assert REDACTED_PLACEHOLDER in redacted


def test_phase0_redact_token_replaces_all_occurrences() -> None:
    token = "ghp_AAA"
    text = f"{token} ... and again {token} and once more {token}"
    redacted = _redact_token(text, token)
    assert token not in redacted
    assert redacted.count(REDACTED_PLACEHOLDER) == 3


def test_phase0_redact_token_handles_none_text() -> None:
    """text=None → "" 반환 (직렬화 안전성)."""
    assert _redact_token(None, "ghp_X") == ""  # type: ignore[arg-type]


def test_phase0_redact_token_empty_token_passthrough() -> None:
    """token 이 빈 문자열이면 redaction 의미 없음 → 원본 그대로 반환.

    빈 문자열로 replace 호출 시 무한 치환 등의 부작용을 방지하는 가드.
    """
    text = "no token here, just plain text"
    assert _redact_token(text, "") == text


def test_phase0_redact_token_case_sensitive() -> None:
    """fine-grained PAT 은 case-sensitive — 대소문자 다르면 매칭 안 됨 (의도된 동작)."""
    token = "ghp_AbCdEf"
    text = f"upper {token.upper()} mixed {token}"
    redacted = _redact_token(text, token)
    # 원본 토큰만 redact, 대문자 변형은 그대로 (별개 시크릿 가능성).
    assert token not in redacted
    assert token.upper() in redacted


# ─── D. token hash (audit 박제용) ────────────────────────────────────────────
def test_phase0_hash_token_returns_12_char_hex_prefix() -> None:
    token = "github_pat_FAKE_OWNER_TOKEN"
    h = _hash_token(token)
    assert len(h) == 12
    # sha256 hex prefix 검증
    expected_full = hashlib.sha256(token.encode("utf-8")).hexdigest()
    assert h == expected_full[:12]
    # 토큰 값 자체는 hash 에 들어가지 않음
    assert token not in h


def test_phase0_hash_token_different_tokens_different_hashes() -> None:
    h1 = _hash_token("ghp_AAA")
    h2 = _hash_token("ghp_BBB")
    assert h1 != h2


def test_phase0_hash_token_deterministic() -> None:
    """동일 token 은 동일 hash — dedupe / 비교에 사용 가능."""
    token = "github_pat_REPEAT"
    assert _hash_token(token) == _hash_token(token)
