import json
import logging
import os
import socket
import traceback
from datetime import datetime, timezone
from typing import Any
from urllib.parse import urlsplit


_STANDARD_RECORD_ATTRS = {
    "args",
    "asctime",
    "created",
    "exc_info",
    "exc_text",
    "filename",
    "funcName",
    "levelname",
    "levelno",
    "lineno",
    "message",
    "module",
    "msecs",
    "msg",
    "name",
    "pathname",
    "process",
    "processName",
    "relativeCreated",
    "stack_info",
    "taskName",
    "thread",
    "threadName",
}


def _json_default(value: Any) -> str:
    return repr(value)


def _utc_timestamp(created: float) -> str:
    return datetime.fromtimestamp(created, tz=timezone.utc).isoformat(timespec="milliseconds")


def _split_client_addr(client_addr: Any) -> tuple[str | None, int | None]:
    if not isinstance(client_addr, str):
        return None, None

    host, sep, port = client_addr.rpartition(":")
    if not sep:
        return client_addr, None
    try:
        return host, int(port)
    except ValueError:
        return client_addr, None


class JsonFormatter(logging.Formatter):
    """
    Emit one JSON object per log line for container log collectors.

    Uvicorn access logs carry structured fields in record.args; keep using
    uvicorn's own access logger and only unpack those fields at format time.
    """

    def __init__(self, *args, service_name: str | None = None, environment: str | None = None, **kwargs):
        super().__init__(*args, **kwargs)
        self.service_name = service_name or os.getenv("SERVICE_NAME", "flower")
        self.environment = environment or os.getenv("ENVIRONMENT") or os.getenv("DJANGO_ENV") or ""
        self.hostname = socket.gethostname()

    def format(self, record: logging.LogRecord) -> str:
        event = {
            "timestamp": _utc_timestamp(record.created),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
            "service": self.service_name,
            "environment": self.environment,
            "hostname": self.hostname,
            "module": record.module,
            "function": record.funcName,
            "line": record.lineno,
            "process": record.process,
            "process_name": record.processName,
            "thread": record.threadName,
        }

        if record.name == "uvicorn.access":
            event.update(self._format_uvicorn_access(record))

        if record.exc_info:
            exc_type, exc_value, _tb = record.exc_info
            event["exception"] = {
                "type": exc_type.__name__ if exc_type else None,
                "message": str(exc_value) if exc_value else None,
                "stacktrace": "".join(traceback.format_exception(*record.exc_info)),
            }
        elif record.exc_text:
            event["exception"] = {"stacktrace": record.exc_text}

        if record.stack_info:
            event["stack_info"] = record.stack_info

        extra = self._collect_extra(record)
        if extra:
            event["extra"] = extra

        return json.dumps(event, ensure_ascii=False, default=_json_default, separators=(",", ":"))

    def _format_uvicorn_access(self, record: logging.LogRecord) -> dict[str, Any]:
        if not isinstance(record.args, tuple) or len(record.args) != 5:
            return {}

        client_addr, method, full_path, http_version, status_code = record.args
        split_result = urlsplit(str(full_path))
        client_host, client_port = _split_client_addr(client_addr)

        return {
            "client_addr": client_addr,
            "client_host": client_host,
            "client_port": client_port,
            "method": method,
            "full_path": full_path,
            "path": split_result.path or str(full_path),
            "query_string": split_result.query,
            "http_version": http_version,
            "status_code": int(status_code),
        }

    def _collect_extra(self, record: logging.LogRecord) -> dict[str, Any]:
        extra = {}
        for key, value in record.__dict__.items():
            if key.startswith("_") or key in _STANDARD_RECORD_ATTRS:
                continue
            extra[key] = value
        return extra


class MaxLevelFilter(logging.Filter):
    """Allow records below the configured level."""

    def __init__(self, max_level: str | int):
        super().__init__()
        self.max_level = logging._checkLevel(max_level)

    def filter(self, record: logging.LogRecord) -> bool:
        return record.levelno < self.max_level
