"""Tests for config/logging_config.py."""

import json
import logging
from pathlib import Path
from unittest.mock import patch

from loguru import logger

from free_claude_code.config import logging_config
from free_claude_code.config.logging_config import configure_logging


def test_configure_logging_creates_parent_directories(tmp_path) -> None:
    """Nested log path: parent directories are created before truncating."""
    log_file = tmp_path / "nested" / "dir" / "app.log"
    configure_logging(str(log_file), force=True)
    assert log_file.is_file()


def test_configure_logging_writes_json_to_file(tmp_path):
    """configure_logging writes JSON lines to the specified file."""
    log_file = str(tmp_path / "test.log")
    configure_logging(log_file, force=True)

    # Emit a log via stdlib (intercepted to loguru)
    logger = logging.getLogger("test.module")
    logger.info("Test message for JSON")

    # Force flush - loguru may buffer
    from loguru import logger as loguru_logger

    loguru_logger.complete()

    content = Path(log_file).read_text(encoding="utf-8")
    lines = [line for line in content.strip().split("\n") if line]
    assert len(lines) >= 1

    # Each line should be valid JSON
    for line in lines:
        record = json.loads(line)
        assert "text" in record or "message" in record or "record" in record


def test_configure_logging_idempotent(tmp_path):
    """configure_logging is idempotent - safe to call twice with force."""
    log_file = str(tmp_path / "test.log")
    configure_logging(log_file, force=True)
    configure_logging(log_file, force=True)  # Should not raise

    logger = logging.getLogger("test.idempotent")
    logger.info("After second configure")


def test_configure_logging_replaces_sink_when_path_changes(tmp_path):
    """A path-only restart switches destinations without truncating either file."""
    first_log = tmp_path / "first.log"
    second_log = tmp_path / "nested" / "second.log"
    configure_logging(first_log, force=True)

    logger.info("first destination")
    from loguru import logger as loguru_logger

    loguru_logger.complete()
    configure_logging(second_log)
    logger.info("second destination")
    loguru_logger.complete()

    first_text = first_log.read_text(encoding="utf-8")
    second_text = second_log.read_text(encoding="utf-8")
    assert "first destination" in first_text
    assert "second destination" not in first_text
    assert "second destination" in second_text


def test_telegram_bot_token_redacted_in_message_field(tmp_path) -> None:
    log_file = str(tmp_path / "redact.log")
    configure_logging(log_file, force=True, verbose_third_party=False)
    token = "123456:ABCDEF-ghij-klm"
    logger.info("Calling {}", f"https://api.telegram.org/bot{token}/getMe")
    logger.complete()
    text = Path(log_file).read_text(encoding="utf-8")
    assert token not in text
    assert "bot<redacted>/" in text or "redacted" in text


def test_bearer_substring_redacted_in_log_file(tmp_path) -> None:
    log_file = str(tmp_path / "bearer.log")
    configure_logging(log_file, force=True, verbose_third_party=False)
    secret = "ya29.secret-token-abc"
    logger.info("Request headers: Authorization: Bearer {}", secret)
    logger.complete()
    text = Path(log_file).read_text(encoding="utf-8")
    assert secret not in text
    assert "Bearer" in text


def test_httpx_logger_quieted_when_not_verbose_third_party(tmp_path) -> None:
    log_file = str(tmp_path / "quiet.log")
    configure_logging(log_file, force=True, verbose_third_party=False)
    assert logging.getLogger("httpx").level >= logging.WARNING
    assert logging.getLogger("httpcore").level >= logging.WARNING


def test_httpx_resets_to_notset_when_verbose_third_party(tmp_path) -> None:
    log_file = str(tmp_path / "verbose.log")
    configure_logging(log_file, force=True, verbose_third_party=True)
    assert logging.getLogger("httpx").level == logging.NOTSET


def test_configure_logging_respects_level(tmp_path) -> None:
    """INFO level suppresses DEBUG messages from the file sink."""
    log_file = str(tmp_path / "level.log")
    configure_logging(log_file, force=True, level="INFO")

    logger.debug("should not appear")
    logger.info("should appear")
    logger.complete()

    text = Path(log_file).read_text(encoding="utf-8")
    assert "should appear" in text
    assert "should not appear" not in text


def test_configure_logging_defaults_to_info(tmp_path) -> None:
    """Customer default keeps lifecycle logs while suppressing debug diagnostics."""
    log_file = str(tmp_path / "default.log")
    configure_logging(log_file, force=True)

    logger.debug("debug message")
    logger.info("info message")
    logger.complete()

    text = Path(log_file).read_text(encoding="utf-8")
    assert "debug message" not in text
    assert "info message" in text


def test_file_sink_bounds_rotated_log_retention(tmp_path) -> None:
    """Five archives plus the active 50 MB file bound normal disk usage."""
    log_file = tmp_path / "bounded.log"

    with patch.object(logging_config.logger, "add", return_value=1) as add:
        logging_config._add_file_sink(log_file, "INFO")

    assert add.call_args.kwargs["rotation"] == "50 MB"
    assert add.call_args.kwargs["retention"] == 5


def test_configure_logging_handles_level_change_on_restart(tmp_path) -> None:
    """Restart with a different level replaces the sink without truncating."""
    log_file = str(tmp_path / "restart.log")

    # First call: DEBUG level
    configure_logging(log_file, force=True, level="DEBUG")
    logger.debug("first debug")
    logger.complete()

    # Second call: change to INFO (simulates supervised restart)
    configure_logging(log_file, level="INFO")
    logger.debug("second debug")
    logger.info("second info")
    logger.complete()

    text = Path(log_file).read_text(encoding="utf-8")
    # First DEBUG message was written before the level change
    assert "first debug" in text
    # Second DEBUG is suppressed by the new INFO level
    assert "second debug" not in text
    # INFO messages appear regardless
    assert "second info" in text


def test_configure_logging_skips_when_level_unchanged(tmp_path) -> None:
    """When level hasn't changed, the existing sink is reused."""
    log_file = str(tmp_path / "same.log")
    configure_logging(log_file, force=True, level="WARNING")
    logger.warning("first warning")
    logger.complete()

    # Second call with same level — should be a no-op for the sink
    configure_logging(log_file, level="WARNING")
    logger.warning("second warning")
    logger.complete()

    text = Path(log_file).read_text(encoding="utf-8")
    assert "first warning" in text
    assert "second warning" in text


def test_configure_logging_updates_verbosity_on_same_level(tmp_path) -> None:
    """When verbose_third_party changes but level stays the same, third-party
    logger levels are updated without touching the file sink."""
    log_file = str(tmp_path / "verbosity.log")

    # Start with verbosity off (third-party loggers at WARNING)
    configure_logging(log_file, force=True, level="DEBUG", verbose_third_party=False)
    assert logging.getLogger("httpx").level >= logging.WARNING
    assert logging.getLogger("httpcore").level >= logging.WARNING
    assert logging.getLogger("telegram").level >= logging.WARNING

    # Restart with same level but verbosity on
    configure_logging(log_file, level="DEBUG", verbose_third_party=True)
    assert logging.getLogger("httpx").level == logging.NOTSET
    assert logging.getLogger("httpcore").level == logging.NOTSET
    assert logging.getLogger("telegram").level == logging.NOTSET

    # Log file sink still works
    logger.info("still logging")
    logger.complete()
    assert "still logging" in Path(log_file).read_text(encoding="utf-8")
