from __future__ import annotations

from collections import deque
from datetime import UTC, datetime
import hashlib
from pathlib import Path
import re

from app.core.config import SERVER_DIR
from app.schemas.system_log import SystemLogEntryRead, SystemLogFileRead, SystemLogTailRead


ANSI_PATTERN = re.compile(r"\x1b\[[0-9;]*m")
STRUCTURED_LINE_PATTERN = re.compile(
    r"^(?P<timestamp>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\s+\|\s+"
    r"(?P<level>[A-Z]+)\s+\|\s+(?P<logger>[^|]+?)\s+\|\s+(?P<message>.*)$"
)
HTTP_ACCESS_PATTERN = re.compile(
    r"^(?P<method>[A-Z]+)\s+(?P<path>\S+)\s+(?P<status_code>\d{3})\s+"
    r"(?P<duration_ms>\d+(?:\.\d+)?)ms(?:\s+request_id=(?P<request_id>\S+))?$"
)


class SystemLogService:
    def __init__(self, *, log_dir: Path | None = None) -> None:
        self.log_dir = Path(log_dir or (SERVER_DIR / "logs")).resolve()

    def list_files(self) -> list[SystemLogFileRead]:
        self.log_dir.mkdir(parents=True, exist_ok=True)
        files = [
            self._serialize_file(path)
            for path in self.log_dir.iterdir()
            if path.is_file() and not path.name.startswith(".")
        ]
        return sorted(
            files,
            key=lambda item: (item.updated_at or datetime.fromtimestamp(0, tz=UTC)),
            reverse=True,
        )

    def read_tail(self, name: str, *, line_limit: int = 300) -> SystemLogTailRead:
        path = self._resolve_file(name)
        line_buffer: deque[str] = deque(maxlen=max(1, min(line_limit, 1000)))

        with path.open("r", encoding="utf-8", errors="replace") as stream:
            for line in stream:
                line_buffer.append(line.rstrip("\n"))

        file_meta = self._serialize_file(path)
        return SystemLogTailRead(
            name=file_meta.name,
            size_bytes=file_meta.size_bytes,
            updated_at=file_meta.updated_at,
            line_count=len(line_buffer),
            lines=list(line_buffer),
        )

    def list_entries(self, *, entry_limit: int = 300, line_limit_per_file: int = 1200) -> list[SystemLogEntryRead]:
        self.log_dir.mkdir(parents=True, exist_ok=True)
        entries: list[SystemLogEntryRead] = []

        for file_meta in self.list_files():
            path = self._resolve_file(file_meta.name)
            lines = self._read_tail_lines(path, line_limit=max(1, min(line_limit_per_file, 5000)))
            entries.extend(self._parse_entries(path.name, lines))

        entries.sort(
            key=lambda item: (
                item.timestamp.timestamp() if item.timestamp else 0,
                item.source_file,
                item.line_number,
            ),
            reverse=True,
        )
        return entries[: max(1, min(entry_limit, 1000))]

    def get_entry(self, entry_id: str) -> SystemLogEntryRead:
        normalized_id = str(entry_id or "").strip()
        if not normalized_id:
            raise FileNotFoundError("日志记录 ID 不能为空。")

        for entry in self.list_entries(entry_limit=1000, line_limit_per_file=5000):
            if entry.id == normalized_id:
                return entry

        raise FileNotFoundError(normalized_id)

    def _resolve_file(self, name: str) -> Path:
        normalized_name = Path(str(name or "").strip()).name
        if not normalized_name:
            raise FileNotFoundError("日志文件名不能为空。")

        path = (self.log_dir / normalized_name).resolve()
        if path.parent != self.log_dir or not path.exists() or not path.is_file():
            raise FileNotFoundError(normalized_name)
        return path

    @staticmethod
    def _read_tail_lines(path: Path, *, line_limit: int) -> list[tuple[int, str]]:
        line_buffer: deque[tuple[int, str]] = deque(maxlen=line_limit)
        with path.open("r", encoding="utf-8", errors="replace") as stream:
            for index, line in enumerate(stream, start=1):
                line_buffer.append((index, line.rstrip("\n")))
        return list(line_buffer)

    def _parse_entries(self, source_file: str, lines: list[tuple[int, str]]) -> list[SystemLogEntryRead]:
        entries: list[SystemLogEntryRead] = []
        current: dict[str, object] | None = None

        for line_number, raw_line in lines:
            clean_line = ANSI_PATTERN.sub("", raw_line)
            match = STRUCTURED_LINE_PATTERN.match(clean_line)

            if match:
                if current is not None:
                    entries.append(self._build_entry(source_file, current))
                current = {
                    "line_number": line_number,
                    "timestamp": self._parse_timestamp(match.group("timestamp")),
                    "level": match.group("level").strip(),
                    "logger": match.group("logger").strip(),
                    "message": match.group("message").strip(),
                    "raw_lines": [clean_line],
                    "parse_status": "parsed",
                }
                continue

            if current is not None:
                current["raw_lines"].append(clean_line)
                continue

            if clean_line.strip():
                current = {
                    "line_number": line_number,
                    "timestamp": None,
                    "level": "UNKNOWN",
                    "logger": "",
                    "message": clean_line.strip(),
                    "raw_lines": [clean_line],
                    "parse_status": "unparsed",
                }
                entries.append(self._build_entry(source_file, current))
                current = None

        if current is not None:
            entries.append(self._build_entry(source_file, current))

        return entries

    def _build_entry(self, source_file: str, payload: dict[str, object]) -> SystemLogEntryRead:
        message = str(payload["message"])
        logger = str(payload["logger"])
        level = str(payload["level"])
        http_match = HTTP_ACCESS_PATTERN.match(message)
        method = http_match.group("method") if http_match else ""
        path = http_match.group("path") if http_match else ""
        status_code = int(http_match.group("status_code")) if http_match else None
        duration_ms = float(http_match.group("duration_ms")) if http_match else None
        request_id = http_match.group("request_id") if http_match and http_match.group("request_id") else ""
        event_type = self._resolve_event_type(logger, level, http_match is not None)
        outcome = self._resolve_outcome(level, status_code)
        summary = self._build_summary(
            event_type=event_type,
            message=message,
            method=method,
            path=path,
            status_code=status_code,
            duration_ms=duration_ms,
        )
        raw = "\n".join(str(line) for line in payload["raw_lines"])
        fingerprint = f"{source_file}:{payload['line_number']}:{payload['timestamp']}:{raw}"
        entry_id = hashlib.sha1(fingerprint.encode("utf-8")).hexdigest()[:16]

        return SystemLogEntryRead(
            id=entry_id,
            source_file=source_file,
            line_number=int(payload["line_number"]),
            timestamp=payload["timestamp"],
            level=level,
            logger=logger,
            message=message,
            request_id=request_id,
            method=method,
            path=path,
            status_code=status_code,
            duration_ms=duration_ms,
            event_type=event_type,
            outcome=outcome,
            summary=summary,
            parse_status=str(payload["parse_status"]),
            raw=raw,
        )

    @staticmethod
    def _parse_timestamp(value: str) -> datetime | None:
        try:
            return datetime.strptime(value, "%Y-%m-%d %H:%M:%S")
        except ValueError:
            return None

    @staticmethod
    def _resolve_event_type(logger: str, level: str, is_http_access: bool) -> str:
        if is_http_access or logger == "app.middleware.access":
            return "HTTP 请求"
        if level in {"ERROR", "CRITICAL"}:
            return "系统异常"
        if level == "WARNING":
            return "系统告警"
        return "运行日志"

    @staticmethod
    def _resolve_outcome(level: str, status_code: int | None) -> str:
        if status_code is not None:
            if status_code >= 500:
                return "失败"
            if status_code >= 400:
                return "异常"
            return "成功"
        if level in {"ERROR", "CRITICAL"}:
            return "失败"
        if level == "WARNING":
            return "告警"
        if level in {"INFO", "DEBUG"}:
            return "成功"
        return "未知"

    @staticmethod
    def _build_summary(
        *,
        event_type: str,
        message: str,
        method: str,
        path: str,
        status_code: int | None,
        duration_ms: float | None,
    ) -> str:
        if method and path and status_code is not None:
            return f"{method} {path} 返回 {status_code}，耗时 {duration_ms or 0:.1f}ms"
        if len(message) <= 96:
            return message
        return f"{message[:96]}..."

    @staticmethod
    def _serialize_file(path: Path) -> SystemLogFileRead:
        stat = path.stat()
        return SystemLogFileRead(
            name=path.name,
            size_bytes=stat.st_size,
            updated_at=datetime.fromtimestamp(stat.st_mtime, tz=UTC),
        )
