from __future__ import annotations

import json
import logging
import logging.handlers
import sys
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional

_LOGGER_NAME = "mockmesh"
_MAX_BYTES = 5 * 1024 * 1024
_BACKUP_COUNT = 5


# ---------------------------------------------------------------------------
# Formatters
# ---------------------------------------------------------------------------

class _JsonFormatter(logging.Formatter):
    def format(self, record: logging.LogRecord) -> str:
        payload: dict[str, Any] = {
            "timestamp": datetime.now(tz=timezone.utc).isoformat(),
            "level": record.levelname,
            "logger": record.name,
            "message": record.getMessage(),
        }
        for k, v in record.__dict__.items():
            if k.startswith("mm_"):
                payload[k[3:]] = v
        if record.exc_info:
            payload["exception"] = self.formatException(record.exc_info)
        return json.dumps(payload)


class _HumanFormatter(logging.Formatter):
    def format(self, record: logging.LogRecord) -> str:
        ts = datetime.now(tz=timezone.utc).strftime("%H:%M:%S")
        extras = {k[3:]: v for k, v in record.__dict__.items() if k.startswith("mm_")}
        line = f"[{ts}] {record.levelname:<7} {record.name} – {record.getMessage()}"
        if extras:
            line += "  " + " ".join(f"{k}={v}" for k, v in extras.items())
        return line


# ---------------------------------------------------------------------------
# Public API
# ---------------------------------------------------------------------------

def get_logger(name: Optional[str] = None) -> logging.Logger:
    return logging.getLogger(f"{_LOGGER_NAME}.{name}" if name else _LOGGER_NAME)


def configure_logging(
    level: int = logging.DEBUG,
    console_log: bool = False,
    json_console: bool = True,
    log_dir: Optional[Path | str] = None,
) -> Path:
    root = logging.getLogger(_LOGGER_NAME)
    root.setLevel(level)
    root.propagate = False

    log_path_dir = Path(log_dir) if log_dir else Path.home() / ".mockmesh" / "logs"
    log_file = log_path_dir / "mockmesh.log"

    existing_types = {type(h) for h in root.handlers}

    if console_log:
        if logging.StreamHandler not in existing_types:
            ch = logging.StreamHandler(sys.stdout)
            ch.setFormatter(_JsonFormatter() if json_console else _HumanFormatter())
            root.addHandler(ch)
    else:
        to_remove = [h for h in root.handlers if type(h) is logging.StreamHandler]
        for h in to_remove:
            root.removeHandler(h)

    if logging.handlers.RotatingFileHandler not in existing_types:
        try:
            log_path_dir.mkdir(parents=True, exist_ok=True)
            fh = logging.handlers.RotatingFileHandler(
                filename=str(log_file),
                maxBytes=_MAX_BYTES,
                backupCount=_BACKUP_COUNT,
                encoding="utf-8",
            )
            fh.setFormatter(_JsonFormatter())
            root.addHandler(fh)

            root.info("Log file initialised",
                      extra={"mm_log_file": str(log_file)})

        except OSError as exc:
            if not any(type(h) is logging.StreamHandler for h in root.handlers):
                fh_fallback = logging.StreamHandler(sys.stdout)
                fh_fallback.setFormatter(_JsonFormatter())
                root.addHandler(fh_fallback)
            root.warning("Log file could not be created – console fallback",
                         extra={"mm_error": str(exc), "mm_path": str(log_file)})

    return log_file
