# ///////////////////////////////////////////////////////////////
# EZPL - Log Parser Utility
# Project: ezpl
# ///////////////////////////////////////////////////////////////

"""
Log parser utility for CLI operations.

This module provides functionality to parse and analyze log files
generated by Ezpl's FileLogger.
"""

from __future__ import annotations

# ///////////////////////////////////////////////////////////////
# IMPORTS
# ///////////////////////////////////////////////////////////////
# Standard library imports
import re
from collections.abc import Iterator
from datetime import datetime
from pathlib import Path
from typing import Any

# ///////////////////////////////////////////////////////////////
# CLASSES
# ///////////////////////////////////////////////////////////////


class LogEntry:
    """
    Represents a single log entry parsed from a log file.
    """

    # ///////////////////////////////////////////////////////////////
    # INIT
    # ///////////////////////////////////////////////////////////////

    def __init__(
        self,
        timestamp: datetime | None,
        level: str,
        module: str,
        function: str,
        line: str,
        message: str,
        raw_line: str,
        line_number: int,
    ) -> None:
        """
        Initialize a log entry.

        Args:
            timestamp: Parsed timestamp or None
            level: Log level
            module: Module name
            function: Function name
            line: Line number
            message: Log message
            raw_line: Original raw line from file
            line_number: Line number in file
        """
        self.timestamp = timestamp
        self.level = level
        self.module = module
        self.function = function
        self.line = line
        self.message = message
        self.raw_line = raw_line
        self.line_number = line_number

    # ///////////////////////////////////////////////////////////////
    # REPRESENTATION METHODS
    # ///////////////////////////////////////////////////////////////

    def __str__(self) -> str:
        """String representation of the log entry."""
        return self.raw_line

    def __repr__(self) -> str:
        """Detailed string representation."""
        return (
            f"LogEntry(timestamp={self.timestamp}, level={self.level}, "
            f"module={self.module}, function={self.function}, line={self.line})"
        )

    def to_dict(self) -> dict[str, Any]:
        """Convert log entry to dictionary."""
        return {
            "timestamp": self.timestamp.isoformat() if self.timestamp else None,
            "level": self.level,
            "module": self.module,
            "function": self.function,
            "line": self.line,
            "message": self.message,
            "line_number": self.line_number,
        }


class LogParser:
    """
    Parser for Ezpl log files.

    Parses log files with the format:
    YYYY-MM-DD HH:MM:SS | LEVEL | module:function:line - message
    """

    # Pattern pour parser les lignes de log
    LOG_PATTERN = re.compile(
        r"^(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})\s+\|\s+(\w+)\s+\|\s+"
        r"([^:]+):([^:]+):([^\s-]+)\s+-\s+(.+)$"
    )

    # Pattern pour les séparateurs de session
    SEPARATOR_PATTERN = re.compile(r"^##\s*==>\s*(.+)$")

    # ///////////////////////////////////////////////////////////////
    # INIT
    # ///////////////////////////////////////////////////////////////

    def __init__(self, log_file: Path) -> None:
        """
        Initialize the log parser.

        Args:
            log_file: Path to the log file to parse

        Raises:
            FileNotFoundError: If the log file doesn't exist
        """
        self.log_file = Path(log_file)
        if not self.log_file.exists():
            raise FileNotFoundError(f"Log file not found: {log_file}")

    # ///////////////////////////////////////////////////////////////
    # PARSING METHODS
    # ///////////////////////////////////////////////////////////////

    def parse_line(self, line: str, line_number: int) -> LogEntry | None:
        """
        Parse a single line from a log file.

        Args:
            line: Line to parse
            line_number: Line number in file

        Returns:
            LogEntry if line is a valid log entry, None otherwise
        """
        line = line.rstrip("\n\r")
        if not line or line.startswith("#"):
            return None

        match = self.LOG_PATTERN.match(line)
        if not match:
            return None

        try:
            timestamp_str, level, module, function, line_num, message = match.groups()
            timestamp = datetime.strptime(timestamp_str, "%Y-%m-%d %H:%M:%S")
            return LogEntry(
                timestamp=timestamp,
                level=level.strip(),
                module=module,
                function=function,
                line=line_num,
                message=message,
                raw_line=line,
                line_number=line_number,
            )
        except (ValueError, AttributeError):
            return None

    def parse(self) -> Iterator[LogEntry]:
        """
        Parse the entire log file.

        Yields:
            LogEntry objects for each valid log entry
        """
        try:
            with open(self.log_file, encoding="utf-8") as f:
                for line_num, line in enumerate(f, start=1):
                    entry = self.parse_line(line, line_num)
                    if entry:
                        yield entry
        except (OSError, UnicodeDecodeError):
            # Return empty iterator on error
            return
            yield  # type: ignore

    def parse_lines(self, max_lines: int | None = None) -> list[LogEntry]:
        """
        Parse log file and return entries as a list.

        Args:
            max_lines: Maximum number of lines to parse (None for all)

        Returns:
            List of LogEntry objects
        """
        entries = []
        for entry in self.parse():
            entries.append(entry)
            if max_lines and len(entries) >= max_lines:
                break
        return entries

    def get_last_lines(self, n: int) -> list[LogEntry]:
        """
        Get the last N log entries from the file.

        Args:
            n: Number of lines to retrieve

        Returns:
            List of last N LogEntry objects
        """
        all_entries = list(self.parse())
        return all_entries[-n:] if len(all_entries) > n else all_entries

    def filter_by_level(self, level: str) -> Iterator[LogEntry]:
        """
        Filter log entries by level.

        Args:
            level: Log level to filter (case-insensitive)

        Yields:
            LogEntry objects matching the level
        """
        level_upper = level.upper()
        for entry in self.parse():
            if entry.level.upper() == level_upper:
                yield entry

    def search(self, pattern: str, case_sensitive: bool = False) -> Iterator[LogEntry]:
        """
        Search for entries matching a pattern.

        Args:
            pattern: Regex pattern to search for
            case_sensitive: Whether search is case-sensitive

        Yields:
            LogEntry objects matching the pattern
        """
        flags = 0 if case_sensitive else re.IGNORECASE
        regex = re.compile(pattern, flags)

        for entry in self.parse():
            if regex.search(entry.message) or regex.search(entry.raw_line):
                yield entry
