from __future__ import annotations

import json
import logging
from pathlib import Path

from fastapi import FastAPI
from fastapi.testclient import TestClient

from core.config.settings import Settings
from core.logging.config import configure_logging
from core.logging.logger import get_logger
from core.logging.middleware import (
    RequestContextMiddleware,
    register_exception_handlers,
)


def _read_json_lines(path: Path) -> list[dict[str, object]]:
    return [json.loads(line) for line in path.read_text().splitlines() if line.strip()]


def _configure_test_logging(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,
        }
    )
    test_settings = settings.model_copy(update={"runtime": runtime})

    configure_logging(test_settings)


def test_middleware_binds_request_context(tmp_path: Path) -> None:
    _configure_test_logging(tmp_path)

    app = FastAPI()
    app.add_middleware(RequestContextMiddleware)  # type: ignore[arg-type]

    @app.get("/ok")
    async def ok() -> dict[str, str]:
        logger = get_logger("tests.ok")
        logger.info("request accepted", context_key="context_value")
        return {"status": "ok"}

    client = TestClient(app)
    response = client.get("/ok", headers={"X-Request-ID": "req-1234"})

    assert response.status_code == 200
    assert response.headers["X-Request-ID"] == "req-1234"

    log_entries = _read_json_lines(Path(tmp_path) / "app.log")
    entry = next(
        item for item in log_entries if item.get("message") == "request accepted"
    )
    assert entry["message"] == "request accepted"
    assert entry["request_id"] == "req-1234"
    assert entry["method"] == "GET"
    assert entry["path"] == "/ok"
    assert entry["context_key"] == "context_value"

    logging.shutdown()


def test_exception_handler_logs_stack_and_sends_500(tmp_path: Path) -> None:
    _configure_test_logging(tmp_path)

    app = FastAPI()
    app.add_middleware(RequestContextMiddleware)
    register_exception_handlers(app)

    @app.get("/boom")
    async def boom() -> dict[str, str]:
        raise RuntimeError("boom")

    client = TestClient(app, raise_server_exceptions=False)
    response = client.get("/boom", headers={"X-Request-ID": "req-5000"})

    assert response.status_code == 500
    assert response.json()["detail"] == "Internal Server Error"

    error_entries = _read_json_lines(Path(tmp_path) / "errors" / "app.error.log")
    assert error_entries
    entry = error_entries[-1]
    assert entry["level"] == "error"
    assert entry["request_id"] == "req-5000"
    exception = str(entry["exception"])
    assert "Traceback" in exception
    assert "test_fastapi_logging_integration" in exception

    logging.shutdown()


def test_invalid_request_id_is_replaced_and_used_in_error_context(
    tmp_path: Path,
) -> None:
    _configure_test_logging(tmp_path)

    app = FastAPI()
    app.add_middleware(RequestContextMiddleware)
    register_exception_handlers(app)

    @app.get("/boom")
    async def boom() -> dict[str, str]:
        raise RuntimeError("boom")

    client = TestClient(app, raise_server_exceptions=False)
    response = client.get("/boom", headers={"X-Request-ID": "bad"})

    assert response.status_code == 500

    response_request_id = response.headers["X-Request-ID"]
    assert response_request_id != "bad"

    error_entries = _read_json_lines(Path(tmp_path) / "errors" / "app.error.log")
    assert error_entries
    entry = error_entries[-1]
    assert entry["request_id"] == response_request_id
    exception = str(entry["exception"])
    assert "Traceback" in exception

    logging.shutdown()
