from __future__ import annotations

import json
import logging
from collections.abc import Iterator
from pathlib import Path
from typing import cast

import pytest
import structlog

from core.config.settings import Settings
from core.logging.config import build_logging_config, configure_logging


def _get_handlers(config: dict[str, object]) -> dict[str, dict[str, object]]:
    return cast(dict[str, dict[str, object]], config["handlers"])


def test_build_logging_config_time_rotation(tmp_path: Path) -> None:
    settings = Settings()
    runtime = settings.runtime.model_copy(
        update={
            "log_dir": str(tmp_path),
            "log_error_dir": str(tmp_path / "errors"),
            "log_rotation": "time",
        }
    )

    config = build_logging_config(runtime)
    handlers = _get_handlers(config)

    assert handlers["file"]["class"] == "logging.handlers.TimedRotatingFileHandler"
    assert handlers["error"]["class"] == "logging.handlers.TimedRotatingFileHandler"
    assert handlers["error"]["level"] == "ERROR"


def test_build_logging_config_size_rotation(tmp_path: Path) -> None:
    settings = Settings()
    runtime = settings.runtime.model_copy(
        update={
            "log_dir": str(tmp_path),
            "log_error_dir": str(tmp_path / "errors"),
            "log_rotation": "size",
            "log_rotation_max_bytes": 2048,
        }
    )

    config = build_logging_config(runtime)
    handlers = _get_handlers(config)

    assert handlers["file"]["class"] == "logging.handlers.RotatingFileHandler"
    assert handlers["error"]["class"] == "logging.handlers.RotatingFileHandler"
    assert handlers["file"]["maxBytes"] == 2048


def test_build_logging_config_plain_formatter_when_disabled(tmp_path: Path) -> None:
    settings = Settings()
    runtime = settings.runtime.model_copy(
        update={
            "log_dir": str(tmp_path),
            "log_error_dir": str(tmp_path / "errors"),
            "log_json": False,
        }
    )

    config = build_logging_config(runtime)
    handlers = _get_handlers(config)

    assert handlers["file"]["formatter"] == "plain"
    assert handlers["error"]["formatter"] == "plain"


def _read_last_log_entry(log_path: Path) -> dict[str, object]:
    assert log_path.exists(), f"Expected log file at {log_path}"
    entries = [
        json.loads(line) for line in log_path.read_text().splitlines() if line.strip()
    ]
    assert entries, "Expected at least one log entry in app.log"
    return entries[-1]


def _flush_root_handlers() -> None:
    root_logger = logging.getLogger()
    for handler in root_logger.handlers:
        if hasattr(handler, "flush"):
            handler.flush()


@pytest.fixture
def configured_logging(tmp_path: Path) -> Iterator[Path]:
    settings = Settings()
    runtime = settings.runtime.model_copy(
        update={
            "log_dir": str(tmp_path),
            "log_error_dir": str(tmp_path / "errors"),
            "log_rotation": "size",
            "log_rotation_max_bytes": 2048,
            "log_json": True,
        }
    )
    root_logger = logging.getLogger()
    original_handlers = root_logger.handlers[:]
    original_level = root_logger.level

    configure_logging(settings.model_copy(update={"runtime": runtime}))

    yield tmp_path

    for handler in root_logger.handlers:
        handler.close()
    root_logger.handlers = original_handlers
    root_logger.setLevel(original_level)
    structlog.reset_defaults()


def test_stdlib_logging_redacts_sensitive_fields(configured_logging: Path) -> None:
    logger = logging.getLogger("tests.stdlib")
    logger.info("login", extra={"password": "secret", "token": "abc"})

    _flush_root_handlers()

    log_path = configured_logging / "app.log"
    entry = _read_last_log_entry(log_path)

    assert entry["password"] == "[REDACTED]"
    assert entry["token"] == "[REDACTED]"


def test_structlog_redacts_sensitive_fields(configured_logging: Path) -> None:
    logger = structlog.get_logger("tests.structlog")
    logger.info("login", password="secret", token="abc")

    _flush_root_handlers()

    log_path = configured_logging / "app.log"
    entry = _read_last_log_entry(log_path)

    assert entry["password"] == "[REDACTED]"
    assert entry["token"] == "[REDACTED]"
