"""
Structured logging configuration for Cerebion Rivet Server.

Sets up:
- JSON-structured log formatting
- Rotating file handlers (app.log, error.log)
- Console handler with readable format
- Request/response timing middleware
"""
import json
import logging
import logging.handlers
import os
import sys
import time
import uuid
from pathlib import Path
from typing import Callable

from fastapi import Request, Response
from starlette.middleware.base import BaseHTTPMiddleware


class JSONFormatter(logging.Formatter):
    """Format log records as single-line JSON."""

    def format(self, record: logging.LogRecord) -> str:
        log_obj = {
            "timestamp": self.formatTime(record, "%Y-%m-%dT%H:%M:%S"),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
        }
        if record.exc_info:
            log_obj["exception"] = self.formatException(record.exc_info)
        # Include any extra fields passed to the logger
        for key, value in record.__dict__.items():
            if key not in (
                "args", "asctime", "created", "exc_info", "exc_text",
                "filename", "funcName", "id", "levelname", "levelno",
                "lineno", "module", "msecs", "message", "msg", "name",
                "pathname", "process", "processName", "relativeCreated",
                "stack_info", "thread", "threadName", "taskName",
            ):
                log_obj[key] = value
        return json.dumps(log_obj, default=str)


def _get_log_dir() -> Path:
    """Return the log directory, creating it if needed."""
    if getattr(sys, "frozen", False):
        # Running as PyInstaller bundle — write to user data dir, not Program Files
        if sys.platform == "win32":
            base = Path(os.environ.get("APPDATA") or Path.home())
        elif sys.platform == "darwin":
            base = Path.home() / "Library" / "Application Support"
        else:
            base = Path(os.environ.get("XDG_DATA_HOME") or Path.home() / ".local" / "share")
        log_dir = base / "CerebionRivet" / "logs"
    else:
        log_dir = Path(__file__).parent.parent / "data" / "logs"
    log_dir.mkdir(parents=True, exist_ok=True)
    return log_dir


def configure_logging(level: str = None):
    """
    Configure application logging. Call once at startup.

    Sets up:
    - Rotating app.log (INFO+, JSON, 10 MB × 5 backups)
    - Rotating error.log (ERROR+, JSON, 10 MB × 5 backups)
    - Console handler (INFO+, human-readable)
    """
    root_logger = logging.getLogger()
    if root_logger.handlers:
        # Already configured — update level only if requested
        if level:
            root_logger.setLevel(level.upper())
        return

    log_level = getattr(logging, (level or os.getenv("LOG_LEVEL", "INFO")).upper(), logging.INFO)
    root_logger.setLevel(log_level)

    log_dir = _get_log_dir()

    # --- app.log: all messages at INFO+ ---
    app_handler = logging.handlers.RotatingFileHandler(
        log_dir / "app.log",
        maxBytes=10 * 1024 * 1024,   # 10 MB
        backupCount=5,
        encoding="utf-8",
    )
    app_handler.setLevel(logging.INFO)
    app_handler.setFormatter(JSONFormatter())

    # --- error.log: ERROR+ only ---
    error_handler = logging.handlers.RotatingFileHandler(
        log_dir / "error.log",
        maxBytes=10 * 1024 * 1024,
        backupCount=5,
        encoding="utf-8",
    )
    error_handler.setLevel(logging.ERROR)
    error_handler.setFormatter(JSONFormatter())

    # --- console: human-readable ---
    console_handler = logging.StreamHandler(sys.stdout)
    console_handler.setLevel(log_level)
    console_handler.setFormatter(
        logging.Formatter("%(asctime)s [%(levelname)s] %(name)s: %(message)s", "%H:%M:%S")
    )

    root_logger.addHandler(app_handler)
    root_logger.addHandler(error_handler)
    root_logger.addHandler(console_handler)

    # Quiet noisy third-party loggers
    logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
    logging.getLogger("httpx").setLevel(logging.WARNING)
    logging.getLogger("httpcore").setLevel(logging.WARNING)
    logging.getLogger("litellm").setLevel(logging.WARNING)
    logging.getLogger("scanner").setLevel(logging.WARNING)

    logging.getLogger(__name__).info(
        "Logging configured", extra={"log_dir": str(log_dir), "level": logging.getLevelName(log_level)}
    )


class RequestLoggingMiddleware(BaseHTTPMiddleware):
    """
    Log every request with method, path, status code, and duration.
    Attaches a unique request_id for correlation across log lines.
    """

    _logger = logging.getLogger("rivet.http")

    # Paths to skip (health checks etc.)
    _skip_paths = {"/health", "/", "/api/health"}

    async def dispatch(self, request: Request, call_next: Callable) -> Response:
        if request.url.path in self._skip_paths:
            return await call_next(request)

        request_id = str(uuid.uuid4())[:8]
        start = time.perf_counter()

        self._logger.info(
            "Request started",
            extra={
                "request_id": request_id,
                "method": request.method,
                "path": request.url.path,
                "client": str(request.client.host) if request.client else "unknown",
            },
        )

        try:
            response = await call_next(request)
        except Exception as exc:
            duration_ms = round((time.perf_counter() - start) * 1000, 1)
            self._logger.error(
                "Request failed with exception",
                extra={
                    "request_id": request_id,
                    "method": request.method,
                    "path": request.url.path,
                    "duration_ms": duration_ms,
                    "error": str(exc),
                },
                exc_info=True,
            )
            raise

        duration_ms = round((time.perf_counter() - start) * 1000, 1)
        level = logging.WARNING if response.status_code >= 400 else logging.INFO
        self._logger.log(
            level,
            "Request completed",
            extra={
                "request_id": request_id,
                "method": request.method,
                "path": request.url.path,
                "status": response.status_code,
                "duration_ms": duration_ms,
            },
        )
        return response
