"""Zipfile rule implementation - 压缩包规则实现."""

from __future__ import annotations

import json
import re
import zipfile
from pathlib import Path
from typing import Any

try:
    import rarfile
except ImportError:
    rarfile = None

try:
    import py7zr
except ImportError:
    py7zr = None

from ...models.fileinfo import FileInfo, MatchInfo
from ..base import BaseRule


class ZipfileRule(BaseRule):
    """压缩包文件匹配规则 - 对 ZIP、RAR、7Z 等压缩包适用.

    主要分析压缩包内的文件清单，并应用规则匹配。
    """

    SUPPORTED_FORMATS = {".zip": "zip", ".rar": "rar", ".7z": "7z"}  # noqa: RUF012

    def __init__(self, rule_data: dict[str, Any]) -> None:
        """初始化压缩包规则.

        Args:
            rule_data: 包含规则配置的字典
        """
        self.name = rule_data.get("name", "")
        self.pattern = rule_data.get("pattern", "")
        self.is_regex = rule_data.get("regex", False)
        self.case_sensitive = rule_data.get("case_sensitive", False)
        self.context_lines = rule_data.get("context_lines", 3)
        self.description = rule_data.get("description", "")

        if self.is_regex:
            flags = 0 if self.case_sensitive else re.IGNORECASE
            try:
                self.compiled_pattern = re.compile(self.pattern, flags | re.ASCII)
            except re.error as e:
                from ...models.logger import logger

                logger.warning(f"无效的正则表达式模式 '{self.pattern}': {e}")
                self.compiled_pattern = None
        else:
            self.compiled_pattern = None

    def match(self, fileinfo: FileInfo) -> bool:
        """匹配压缩包文件.

        Args:
            fileinfo: 文件信息对象

        Returns
        -------
            如果匹配返回 True，否则返回 False
        """
        file_path = Path(fileinfo.file_path)
        suffix = file_path.suffix.lower()

        if suffix not in self.SUPPORTED_FORMATS:
            return False

        try:
            archive_contents = self._extract_archive_contents(file_path, suffix)
            if not archive_contents:
                return False

            # 将文件列表转换为文本进行匹配
            content_text = "\n".join(archive_contents)
            return self._match_content(content_text)

        except (OSError, ValueError, RuntimeError):
            # 压缩包损坏或无法读取
            return False

    def find_all_matches(self, fileinfo: FileInfo) -> list[MatchInfo]:
        """查找压缩包中的所有匹配项.

        Args:
            fileinfo: 文件信息对象

        Returns
        -------
            匹配结果列表
        """
        matches = []
        file_path = Path(fileinfo.file_path)
        suffix = file_path.suffix.lower()

        if suffix not in self.SUPPORTED_FORMATS:
            return matches

        try:
            # 提取压缩包内的文件列表
            archive_contents = self._extract_archive_contents(file_path, suffix)
            if not archive_contents:
                return matches

            # 将文件列表转换为文本进行匹配
            "\n".join(archive_contents)

            # 应用规则匹配
            if self.is_regex and self.compiled_pattern:
                # 正则表达式匹配
                for line_num, line in enumerate(archive_contents, 1):
                    for match in self.compiled_pattern.finditer(line):
                        match_info = MatchInfo(
                            file_id=fileinfo.id,
                            rule_name=self.name,
                            rule_description=self.description,
                            match_type="regex",
                            analysis_type="zipfile",
                            line_number=line_num,
                            match_text=match.group(),
                            start_pos=match.start(),
                            end_pos=match.end(),
                            context_lines=self._get_context(archive_contents, line_num - 1),
                            metadata=json.dumps({
                                "archive_type": self.SUPPORTED_FORMATS[suffix],
                                "matched_filename": archive_contents[line_num - 1]
                                if line_num > 0 and line_num <= len(archive_contents)
                                else "",
                                "total_files": len(archive_contents),
                            }),
                        )
                        matches.append(match_info)
            else:
                # 文本匹配
                search_text = self.pattern if self.case_sensitive else self.pattern.lower()
                for line_num, line in enumerate(archive_contents, 1):
                    compare_line = line if self.case_sensitive else line.lower()
                    start = 0
                    while True:
                        pos = compare_line.find(search_text, start)
                        if pos == -1:
                            break
                        match_info = MatchInfo(
                            file_id=fileinfo.id,
                            rule_name=self.name,
                            rule_description=self.description,
                            match_type="text",
                            analysis_type="zipfile",
                            line_number=line_num,
                            match_text=line[pos : pos + len(self.pattern)],
                            start_pos=pos,
                            end_pos=pos + len(self.pattern),
                            context_lines=self._get_context(archive_contents, line_num - 1),
                            metadata=json.dumps({
                                "archive_type": self.SUPPORTED_FORMATS[suffix],
                                "matched_filename": archive_contents[line_num - 1]
                                if line_num > 0 and line_num <= len(archive_contents)
                                else "",
                                "total_files": len(archive_contents),
                            }),
                        )
                        matches.append(match_info)
                        start = pos + 1

        except (OSError, ValueError, RuntimeError) as e:
            from ...models.logger import logger

            logger.warning(f"无法读取压缩包 {file_path}: {e}")

        return matches

    def _extract_archive_contents(self, file_path: Path, suffix: str) -> list[str]:
        """提取压缩包内的文件列表.

        Args:
            file_path: 压缩包文件路径
            suffix: 文件扩展名

        Returns
        -------
            文件名列表
        """
        if suffix == ".zip":
            return self._read_zip(file_path)
        if suffix == ".rar":
            return self._read_rar(file_path)
        if suffix == ".7z":
            return self._read_7z(file_path)
        return []

    def _read_zip(self, file_path: Path) -> list[str]:
        """读取 ZIP 压缩包内容."""
        try:
            with zipfile.ZipFile(file_path, "r") as zf:
                return zf.namelist()
        except (zipfile.BadZipFile, OSError):
            return []

    def _read_rar(self, file_path: Path) -> list[str]:
        """读取 RAR 压缩包内容."""
        if rarfile is None:
            return []
        try:
            with rarfile.RarFile(file_path, "r") as rf:
                return [member.filename for member in rf.infolist()]
        except (rarfile.BadRarFile, OSError):
            return []

    def _read_7z(self, file_path: Path) -> list[str]:
        """读取 7Z 压缩包内容."""
        if py7zr is None:
            return []
        try:
            with py7zr.SevenZipFile(file_path, "r") as szf:
                return szf.getnames()
        except (py7zr.Bad7zFile, OSError):
            return []

    def _match_content(self, content: str) -> bool:
        """检查内容是否匹配规则.

        Args:
            content: 压缩包内文件名列表的文本

        Returns
        -------
            如果匹配返回 True
        """
        if not content or not self.pattern:
            return False

        if self.is_regex and self.compiled_pattern:
            return bool(self.compiled_pattern.search(content))
        compare_content = content if self.case_sensitive else content.lower()
        search_text = self.pattern if self.case_sensitive else self.pattern.lower()
        return search_text in compare_content

    def _get_context(self, lines: list[str], line_index: int) -> list[str]:
        """获取匹配项周围的上下文行.

        Args:
            lines: 所有文件名
            line_index: 匹配行的索引

        Returns
        -------
            上下文行列表
        """
        start = max(0, line_index - self.context_lines)
        end = min(len(lines), line_index + self.context_lines + 1)
        return lines[start:end]
