"""Pydantic models for log aggregation.

This module defines the data structures used for log collection,
storage, and querying.
"""

from __future__ import annotations

import re
from datetime import datetime, timedelta
from enum import Enum
from typing import Literal

from pydantic import BaseModel, Field


class LogLevel(str, Enum):
    """Log severity level."""

    DEBUG = "debug"
    INFO = "info"
    WARNING = "warning"
    ERROR = "error"
    CRITICAL = "critical"
    UNKNOWN = "unknown"


class LogSource(str, Enum):
    """Source of the log output."""

    STDOUT = "stdout"
    STDERR = "stderr"
    JOURNAL = "journal"
    FILE = "file"
    UNKNOWN = "unknown"


class LogEntry(BaseModel):
    """A single log entry from a container.

    Represents a parsed log line with metadata about its source,
    timestamp, and severity level.
    """

    container_id: str
    """Container ID in the runtime."""

    container_name: str
    """Human-readable container name."""

    runtime: str
    """Runtime that produced this log (docker, nspawn, libvirt)."""

    timestamp: datetime = Field(default_factory=datetime.now)
    """When the log entry was produced."""

    message: str
    """The log message content."""

    level: LogLevel = LogLevel.UNKNOWN
    """Detected log level."""

    source: LogSource = LogSource.UNKNOWN
    """Source of the log (stdout, stderr, journal, file)."""

    raw: str | None = None
    """Original raw log line before parsing."""

    def to_jsonl(self) -> str:
        """Serialize entry to JSON Lines format."""
        return self.model_dump_json()

    @classmethod
    def from_jsonl(cls, line: str) -> LogEntry:
        """Deserialize entry from JSON Lines format."""
        return cls.model_validate_json(line)

    def matches_query(self, query: LogQuery) -> bool:
        """Check if this entry matches a query."""
        # Time range filter
        if query.since and self.timestamp < query.since:
            return False
        if query.until and self.timestamp > query.until:
            return False

        # Container filter
        if query.containers and self.container_name not in query.containers:
            return False

        # Level filter
        if query.levels and self.level not in query.levels:
            return False

        # Source filter
        if query.sources and self.source not in query.sources:
            return False

        # Text pattern filter
        if query.pattern:
            if query.regex:
                if not re.search(query.pattern, self.message, re.IGNORECASE if not query.case_sensitive else 0):
                    return False
            else:
                if query.case_sensitive:
                    if query.pattern not in self.message:
                        return False
                else:
                    if query.pattern.lower() not in self.message.lower():
                        return False

        return True


class LogQuery(BaseModel):
    """Query parameters for searching logs."""

    since: datetime | None = None
    """Show logs since this time."""

    until: datetime | None = None
    """Show logs until this time."""

    containers: list[str] | None = None
    """Filter by container names (any match)."""

    levels: list[LogLevel] | None = None
    """Filter by log levels (any match)."""

    sources: list[LogSource] | None = None
    """Filter by log sources (any match)."""

    pattern: str | None = None
    """Text pattern to search for."""

    regex: bool = False
    """Treat pattern as regular expression."""

    case_sensitive: bool = False
    """Case-sensitive pattern matching."""

    limit: int | None = None
    """Maximum number of entries to return."""

    offset: int = 0
    """Number of entries to skip (for pagination)."""

    @classmethod
    def last_n_minutes(cls, minutes: int, **kwargs) -> LogQuery:
        """Create a query for logs from the last N minutes."""
        return cls(since=datetime.now() - timedelta(minutes=minutes), **kwargs)

    @classmethod
    def last_n_hours(cls, hours: int, **kwargs) -> LogQuery:
        """Create a query for logs from the last N hours."""
        return cls(since=datetime.now() - timedelta(hours=hours), **kwargs)

    @classmethod
    def errors_only(cls, **kwargs) -> LogQuery:
        """Create a query for error and critical logs only."""
        return cls(levels=[LogLevel.ERROR, LogLevel.CRITICAL], **kwargs)


class RetentionPolicy(BaseModel):
    """Policy for log retention and rotation."""

    max_age_days: int = 7
    """Delete logs older than this many days."""

    max_size_mb: int = 100
    """Maximum total log size in MB per container."""

    max_entries: int | None = None
    """Maximum number of entries per container (None = unlimited)."""

    compress_after_days: int = 1
    """Compress log files older than this many days."""


class LogConfig(BaseModel):
    """Configuration for the log aggregation system."""

    enabled: bool = True
    """Whether log collection is enabled."""

    store_dir: str | None = None
    """Directory for log storage (default: ~/.config/gaol/logs)."""

    retention: RetentionPolicy = Field(default_factory=RetentionPolicy)
    """Log retention policy."""

    collect_interval: float = 5.0
    """Interval in seconds between log collection runs."""

    parse_levels: bool = True
    """Attempt to parse log levels from messages."""

    buffer_size: int = 1000
    """Number of entries to buffer before flushing to disk."""

    include_timestamps: bool = True
    """Include timestamps in collected logs."""


class LogStats(BaseModel):
    """Statistics about stored logs."""

    container_name: str
    """Container name."""

    total_entries: int
    """Total number of log entries."""

    total_size_bytes: int
    """Total size of log files in bytes."""

    oldest_entry: datetime | None = None
    """Timestamp of oldest log entry."""

    newest_entry: datetime | None = None
    """Timestamp of newest log entry."""

    entries_by_level: dict[str, int] = Field(default_factory=dict)
    """Count of entries by log level."""

    entries_by_source: dict[str, int] = Field(default_factory=dict)
    """Count of entries by source."""


# Common log level patterns for parsing
LOG_LEVEL_PATTERNS: dict[LogLevel, list[str]] = {
    LogLevel.DEBUG: ["DEBUG", "DBG", "TRACE", "VERBOSE"],
    LogLevel.INFO: ["INFO", "INF", "NOTICE"],
    LogLevel.WARNING: ["WARN", "WARNING", "WRN"],
    LogLevel.ERROR: ["ERROR", "ERR", "FAIL", "FAILED"],
    LogLevel.CRITICAL: ["CRITICAL", "CRIT", "FATAL", "PANIC", "EMERGENCY"],
}


def parse_log_level(message: str) -> LogLevel:
    """Attempt to parse log level from a message.

    Looks for common log level indicators in the message.

    Args:
        message: The log message to parse.

    Returns:
        Detected LogLevel or UNKNOWN if not detected.
    """
    upper_msg = message.upper()

    # Check for level patterns in priority order (critical first)
    for level in [LogLevel.CRITICAL, LogLevel.ERROR, LogLevel.WARNING, LogLevel.INFO, LogLevel.DEBUG]:
        for pattern in LOG_LEVEL_PATTERNS[level]:
            # Look for pattern as word boundary (not substring of another word)
            if re.search(rf'\b{pattern}\b', upper_msg):
                return level

    return LogLevel.UNKNOWN
