#!/usr/bin/env python3
"""utils/redact.py 테스트 스위트"""

import logging
import sys
from pathlib import Path

import pytest

sys.path.insert(0, str(Path(__file__).parent.parent.parent))

from utils.redact import RedactingFormatter, redact_sensitive_text, setup_redacted_logging


class TestRedactSensitiveText:
    """redact_sensitive_text() 패턴 테스트"""

    def test_passthrough_plain_text(self):
        """일반 텍스트는 변경 없이 통과"""
        assert redact_sensitive_text("hello world") == "hello world"

    def test_none_returns_none(self):
        """None 입력은 None 반환"""
        assert redact_sensitive_text(None) is None  # type: ignore[arg-type]

    def test_empty_string(self):
        """빈 문자열 통과"""
        assert redact_sensitive_text("") == ""

    def test_non_string_coerced(self):
        """비문자열은 str()로 변환 후 처리"""
        result = redact_sensitive_text(42)  # type: ignore[arg-type]
        assert result == "42"

    # --- OpenAI / Anthropic sk- 토큰 ---
    def test_sk_token_masked(self):
        """sk-로 시작하는 API 키 마스킹"""
        text = "key=sk-abcdefghijklmnopqrstuvwxyz1234"
        result = redact_sensitive_text(text)
        assert "sk-abcdefghijklmnopqrstuvwxyz1234" not in result
        assert "sk-abc" in result  # 앞 6자 보존
        assert "1234" in result   # 뒤 4자 보존

    def test_sk_ant_token_masked(self):
        """sk-ant- Anthropic 토큰 마스킹"""
        text = "ANTHROPIC_API_KEY=sk-ant-api03-verylongkeyhere12345678"
        result = redact_sensitive_text(text)
        assert "api03-verylongkeyhere12345678" not in result

    # --- GitHub PAT ---
    def test_github_pat_classic_masked(self):
        """ghp_ GitHub PAT 마스킹"""
        text = "token=ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcd"
        result = redact_sensitive_text(text)
        assert "ghp_ABCDEFGHIJKLMNOPQRSTUVWXYZabcd" not in result

    def test_github_pat_fine_grained_masked(self):
        """github_pat_ 세밀한 PAT 마스킹"""
        text = "github_pat_ABCDEFGHIJKLMNOPQRSTUVWXYZ123456"
        result = redact_sensitive_text(text)
        assert "ABCDEFGHIJKLMNOPQRSTUVWXYZ123456" not in result

    # --- AWS ---
    def test_aws_access_key_masked(self):
        """AKIA AWS Access Key ID 마스킹"""
        text = "AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE"
        result = redact_sensitive_text(text)
        assert "AKIAIOSFODNN7EXAMPLE" not in result

    # --- Stripe ---
    def test_stripe_live_key_masked(self):
        """sk_live_ Stripe 키 마스킹"""
        text = "STRIPE_KEY=sk_live_abcdefghijklmnopqrst"
        result = redact_sensitive_text(text)
        assert "sk_live_abcdefghijklmnopqrst" not in result

    # --- ENV 할당 패턴 ---
    def test_env_assignment_api_key(self):
        """KEY=value 형태 ENV 할당 마스킹"""
        text = "OPENAI_API_KEY=super_secret_key_value_here_12345"
        result = redact_sensitive_text(text)
        assert "super_secret_key_value_here_12345" not in result
        assert "OPENAI_API_KEY=" in result

    def test_env_assignment_quoted(self):
        """KEY='value' 따옴표 포함 ENV 할당 마스킹"""
        text = "DATABASE_PASSWORD='my_very_long_secret_password_xyz'"
        result = redact_sensitive_text(text)
        assert "my_very_long_secret_password_xyz" not in result

    # --- JSON 필드 패턴 ---
    def test_json_api_key_field(self):
        """JSON apiKey 필드 마스킹"""
        text = '{"apiKey": "supersecretlongvalue12345678"}'
        result = redact_sensitive_text(text)
        assert "supersecretlongvalue12345678" not in result

    def test_json_token_field(self):
        """JSON token 필드 마스킹"""
        text = '{"token": "bearer_token_value_very_long_string"}'
        result = redact_sensitive_text(text)
        assert "bearer_token_value_very_long_string" not in result

    # --- Authorization 헤더 ---
    def test_auth_bearer_header(self):
        """Authorization: Bearer 헤더 마스킹"""
        text = "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp"
        result = redact_sensitive_text(text)
        assert "eyJhbGciOiJSUzI1NiIsInR5cCI6Ikp" not in result
        assert "Authorization: Bearer" in result

    # --- Telegram ---
    def test_telegram_bot_token(self):
        """Telegram bot 토큰 마스킹"""
        text = "bot123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghi"
        result = redact_sensitive_text(text)
        assert "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghi" not in result
        assert "123456789" in result  # 숫자 부분 보존

    # --- Private Key ---
    def test_private_key_block(self):
        """PEM 형식 Private Key 블록 마스킹"""
        text = "-----BEGIN RSA PRIVATE KEY-----\nMIIEpAIBAAKCAQEA\n-----END RSA PRIVATE KEY-----"
        result = redact_sensitive_text(text)
        assert "MIIEpAIBAAKCAQEA" not in result
        assert "[REDACTED PRIVATE KEY]" in result

    # --- DB 연결 문자열 ---
    def test_postgres_connection_string(self):
        """PostgreSQL 연결 문자열 비밀번호 마스킹"""
        text = "postgresql://user:mysecretpassword@localhost:5432/db"
        result = redact_sensitive_text(text)
        assert "mysecretpassword" not in result
        assert "postgresql://user:" in result
        assert "@localhost" in result

    def test_mysql_connection_string(self):
        """MySQL 연결 문자열 비밀번호 마스킹"""
        text = "mysql://admin:p@ssw0rd123@db.example.com/mydb"
        result = redact_sensitive_text(text)
        assert "p@ssw0rd123" not in result

    # --- 짧은 토큰 완전 마스킹 ---
    def test_short_token_fully_masked(self):
        """18자 미만 토큰은 *** 로 완전 마스킹"""
        # ENV 할당을 통해 짧은 값 테스트
        text = "API_KEY=short_val_x"
        result = redact_sensitive_text(text)
        # 짧은 값(< 18자)은 *** 로 치환
        assert "***" in result or "short_val_x" not in result

    def test_env_disabled_via_envvar(self, monkeypatch):
        """DISABLE_REDACT_SECRETS=1 환경변수로 마스킹 비활성화"""
        monkeypatch.setenv("DISABLE_REDACT_SECRETS", "1")
        text = "sk-abcdefghijklmnopqrstuvwxyz1234"
        result = redact_sensitive_text(text)
        # 비활성화되면 원본 유지
        assert result == text


class TestRedactingFormatter:
    """RedactingFormatter 로그 포맷터 테스트"""

    def test_formatter_masks_secrets_in_log(self):
        """로그 메시지에서 비밀 마스킹"""
        formatter = RedactingFormatter(fmt="%(message)s")
        record = logging.LogRecord(
            name="test",
            level=logging.INFO,
            pathname="",
            lineno=0,
            msg="key=sk-abcdefghijklmnopqrstuvwxyz1234",
            args=(),
            exc_info=None,
        )
        output = formatter.format(record)
        assert "sk-abcdefghijklmnopqrstuvwxyz1234" not in output

    def test_formatter_passes_plain_text(self):
        """일반 메시지는 변경 없이 통과"""
        formatter = RedactingFormatter(fmt="%(message)s")
        record = logging.LogRecord(
            name="test",
            level=logging.INFO,
            pathname="",
            lineno=0,
            msg="normal log message",
            args=(),
            exc_info=None,
        )
        output = formatter.format(record)
        assert "normal log message" in output


class TestSetupRedactedLogging:
    """setup_redacted_logging() 통합 테스트"""

    def test_applies_to_existing_logger(self):
        """기존 로거에 RedactingFormatter 적용"""
        logger = logging.getLogger("test.setup_redact")
        logger.handlers.clear()

        handler = logging.StreamHandler()
        handler.setFormatter(logging.Formatter("%(message)s"))
        logger.addHandler(handler)

        setup_redacted_logging(logger)

        # 모든 핸들러에 RedactingFormatter가 적용되어야 함
        for h in logger.handlers:
            assert isinstance(h.formatter, RedactingFormatter)

    def test_applies_to_root_logger_by_default(self):
        """인수 없이 호출 시 루트 로거에 적용"""
        root = logging.getLogger()
        original_handlers = list(root.handlers)

        # 테스트용 핸들러 추가
        test_handler = logging.StreamHandler()
        test_handler.setFormatter(logging.Formatter("%(message)s"))
        root.addHandler(test_handler)

        try:
            setup_redacted_logging()
            for h in root.handlers:
                assert isinstance(h.formatter, RedactingFormatter)
        finally:
            # 테스트 핸들러 정리
            root.removeHandler(test_handler)
            # 원래 핸들러 복원
            for h in list(root.handlers):
                if h not in original_handlers:
                    root.removeHandler(h)

    def test_no_error_on_handler_without_formatter(self):
        """formatter가 없는 핸들러도 오류 없이 처리"""
        logger = logging.getLogger("test.no_formatter")
        logger.handlers.clear()

        handler = logging.StreamHandler()
        # formatter 설정 안 함
        logger.addHandler(handler)

        # 예외 없이 실행되어야 함
        setup_redacted_logging(logger)
        for h in logger.handlers:
            assert isinstance(h.formatter, RedactingFormatter)


if __name__ == "__main__":
    pytest.main([__file__, "-v"])
