"""Structured logging for the agent.

Logs go to stderr (and the rotating agent log file), NEVER stdout. Command
output — especially the JSON emitted by ``discover --full`` — must stay clean on
stdout so it can be piped (e.g. into ``python3 -m json.tool``) without log
pollution. A redact() helper masks token-like values; callers must NEVER pass
raw secrets to the logger. See SECURITY rules in the README.
"""

from __future__ import annotations

import re
import logging
import sys
from logging.handlers import RotatingFileHandler
from pathlib import Path

_LOG_FORMAT = "%(asctime)s %(levelname)-7s %(name)s %(message)s"
_configured = False


class RedactingFilter(logging.Filter):
    """Filter to redact sensitive information (tokens, passwords, bearer headers, etc.) from log messages."""

    PATTERNS = [
        # Match tokens starting with wp_ (e.g. wp_agent_..., wp_install_...)
        (re.compile(r'wp_[a-zA-Z0-9_]+'), 'wp_***'),
        # Match Bearer tokens or Authorization headers
        (re.compile(r'(?i)(bearer\s+)[a-zA-Z0-9_\-\.\+/=]+'), r'\1***'),
        # Match key-value pairs (like password=xyz, token: abc, "password": "xyz")
        (re.compile(r'(?i)(token|password|authorization|bearer|install_token|install\s+token)(["\'\s]*[:=]["\'\s]*)([^\s"\'&,;\n]+)'), r'\1\2***'),
    ]

    def filter(self, record: logging.LogRecord) -> bool:
        if isinstance(record.msg, str):
            msg = record.msg
            for pattern, repl in self.PATTERNS:
                msg = pattern.sub(repl, msg)
            record.msg = msg

        if record.args:
            if isinstance(record.args, dict):
                new_args = {}
                for k, v in record.args.items():
                    if isinstance(v, str):
                        for pattern, repl in self.PATTERNS:
                            v = pattern.sub(repl, v)
                    new_args[k] = v
                record.args = new_args
            elif isinstance(record.args, (list, tuple)):
                new_args = []
                for arg in record.args:
                    if isinstance(arg, str):
                        for pattern, repl in self.PATTERNS:
                            arg = pattern.sub(repl, arg)
                    new_args.append(arg)
                record.args = tuple(new_args)

        return True


class LoggerNameFilter(logging.Filter):
    """Filter log records by their logger name prefix."""

    def __init__(self, include: list[str] | None = None, exclude: list[str] | None = None) -> None:
        super().__init__()
        self.include = include or []
        self.exclude = exclude or []

    def filter(self, record: logging.LogRecord) -> bool:
        for name in self.exclude:
            if record.name == name or record.name.startswith(name + "."):
                return False
        if not self.include:
            return True
        for name in self.include:
            if record.name == name or record.name.startswith(name + "."):
                return True
        return False


def setup_logging(log_file: Path | None = None, level: int = logging.INFO) -> None:
    """Configure root logging once. Safe to call multiple times."""
    global _configured
    if _configured:
        return

    root = logging.getLogger("wolfpanel")
    root.setLevel(level)
    root.addFilter(RedactingFilter())
    formatter = logging.Formatter(_LOG_FORMAT)

    # stderr, not stdout: keeps command output (JSON) clean for piping.
    stream = logging.StreamHandler(sys.stderr)
    stream.setFormatter(formatter)
    root.addHandler(stream)

    # File logging is best-effort: never crash the agent because a log
    # directory is missing or unwritable (common on first run / dev machines).
    if log_file is not None:
        try:
            log_file.parent.mkdir(parents=True, exist_ok=True)
            
            # Handler for agent.log (excludes runner, worker, actions)
            file_handler = RotatingFileHandler(
                log_file, maxBytes=5 * 1024 * 1024, backupCount=3, encoding="utf-8"
            )
            file_handler.setFormatter(formatter)
            file_handler.addFilter(
                LoggerNameFilter(
                    exclude=[
                        "wolfpanel.runner",
                        "wolfpanel.worker",
                        "wolfpanel.actions",
                        "wolfpanel.update_action",
                    ]
                )
            )
            root.addHandler(file_handler)

            # Handler for jobs.log (includes only runner, worker, actions)
            jobs_log = log_file.parent / "jobs.log"
            jobs_handler = RotatingFileHandler(
                jobs_log, maxBytes=5 * 1024 * 1024, backupCount=3, encoding="utf-8"
            )
            jobs_handler.setFormatter(formatter)
            jobs_handler.addFilter(
                LoggerNameFilter(
                    include=[
                        "wolfpanel.runner",
                        "wolfpanel.worker",
                        "wolfpanel.actions",
                        "wolfpanel.update_action",
                    ]
                )
            )
            root.addHandler(jobs_handler)
        except OSError as exc:  # pragma: no cover - environment dependent
            root.warning("file logging disabled (%s): %s", log_file, exc)

    _configured = True


def get_logger(name: str) -> logging.Logger:
    """Return a namespaced logger under the 'wolfpanel' root."""
    return logging.getLogger(f"wolfpanel.{name}")


def redact(secret: str | None) -> str:
    """Mask a secret for safe display in logs (never log the full value)."""
    if not secret:
        return "<none>"
    if len(secret) <= 8:
        return "****"
    return f"{secret[:4]}...{secret[-2:]}"
