"""Database manager for filescan - 扫描结果持久化."""

from __future__ import annotations

import json
import sqlite3
from datetime import datetime, timezone
from pathlib import Path
from typing import Any

from pytola_core import DatabaseManager


class ScanDatabase(DatabaseManager):
    """扫描结果 SQLite 数据库管理器.

    特性:
    - 线程安全的数据库操作
    - 连接池管理
    - 自动表结构初始化
    - 事务支持
    """

    def __init__(self, db_path: Path | str, max_connections: int = 10) -> None:
        """初始化数据库连接.

        Args:
            db_path: SQLite 数据库文件路径
            max_connections: 连接池最大连接数
        """
        from pytola_core.persistance.database import PoolConfig

        pool_config = PoolConfig(max_size=max_connections)
        super().__init__(db_path=db_path, pool_config=pool_config)

        # 初始化表结构（使用连接池获取连接）
        with self.pool.get_connection() as conn:
            cursor = conn.cursor()
            self._create_tables(cursor)

    def _create_tables(self, cursor: sqlite3.Cursor) -> None:
        """如果不存在则创建数据库表.

        Args:
            cursor: SQLite 游标
        """
        # 扫描会话表
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS scan_sessions (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                session_name TEXT NOT NULL,
                input_directory TEXT NOT NULL,
                scan_time TEXT NOT NULL,
                total_files INTEGER,
                files_with_matches INTEGER,
                rules_count INTEGER,
                file_types_scanned TEXT,
                use_pdf_ocr INTEGER DEFAULT 0,
                use_process_pool INTEGER DEFAULT 0,
                duration_seconds REAL,
                created_at TEXT NOT NULL
            )
        """)

        # 匹配文件表
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS matched_files (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                session_id INTEGER NOT NULL,
                file_path TEXT NOT NULL,
                file_type TEXT,
                file_size INTEGER,
                match_count INTEGER,
                processing_time_seconds REAL,
                metadata TEXT,
                FOREIGN KEY (session_id) REFERENCES scan_sessions (id) ON DELETE CASCADE
            )
        """)

        # 匹配详情表
        cursor.execute("""
            CREATE TABLE IF NOT EXISTS matches (
                id INTEGER PRIMARY KEY AUTOINCREMENT,
                file_id INTEGER NOT NULL,
                rule_name TEXT NOT NULL,
                rule_description TEXT,
                match_type TEXT,
                match_text TEXT NOT NULL,
                line_number INTEGER,
                start_pos INTEGER,
                end_pos INTEGER,
                context TEXT,
                FOREIGN KEY (file_id) REFERENCES matched_files (id) ON DELETE CASCADE
            )
        """)

        # 创建索引以提高查询速度
        cursor.execute("""
            CREATE INDEX IF NOT EXISTS idx_matched_files_session
            ON matched_files(session_id)
        """)

        cursor.execute("""
            CREATE INDEX IF NOT EXISTS idx_matches_file
            ON matches(file_id)
        """)

        cursor.execute("""
            CREATE INDEX IF NOT EXISTS idx_matches_rule
            ON matches(rule_name)
        """)

    def _prepare_session_data(self, results: dict[str, Any], session_name: str | None) -> dict[str, Any]:
        """准备会话数据用于插入.

        Args:
            results: 扫描结果字典
            session_name: 可选的自定义会话名称

        Returns
        -------
            会话数据字典
        """
        scan_info = results.get("scan_info", {})
        return {
            "session_name": session_name or f"Scan_{datetime.now(tz=timezone.utc).strftime('%Y%m%d_%H%M%S')}",
            "input_directory": scan_info.get("input_directory", ""),
            "scan_time": scan_info.get("scan_time", ""),
            "total_files": scan_info.get("total_files", 0),
            "files_with_matches": scan_info.get("files_with_matches", 0),
            "rules_count": scan_info.get("rules_count", 0),
            "file_types_scanned": json.dumps(scan_info.get("file_types_scanned", [])),
            "use_pdf_ocr": 1 if scan_info.get("use_pdf_ocr", False) else 0,
            "use_process_pool": 1 if scan_info.get("use_process_pool", False) else 0,
            "duration_seconds": scan_info.get("duration_seconds"),
            "created_at": datetime.now(tz=timezone.utc).isoformat(),
        }

    def _prepare_match_data(self, results: dict[str, Any], session_id: int) -> tuple[list[dict], list[dict]]:
        """准备匹配数据用于插入.

        Args:
            results: 扫描结果字典
            session_id: 会话 ID

        Returns
        -------
            (匹配文件数据，匹配详情数据) 元组
        """
        matched_files_data = []
        matches_data = []

        for match_data in results.get("matches", []):
            # 准备匹配文件数据
            matched_files_data.append({
                "session_id": session_id,
                "file_path": match_data.get("file_path", ""),
                "file_type": match_data.get("file_type", ""),
                "file_size": match_data.get("file_size", 0),
                "match_count": len(match_data.get("matches", [])),
                "processing_time_seconds": match_data.get("metadata", {}).get("processing_time_seconds", 0),
                "metadata": json.dumps(match_data.get("metadata", {})),
            })

            # 准备匹配详情数据
            matches_data.extend(
                {
                    "rule_name": match.get("rule_name", ""),
                    "rule_description": match.get("rule_description", ""),
                    "match_type": match.get("type", ""),
                    "match_text": match.get("match", ""),
                    "line_number": match.get("line_number", 0),
                    "start_pos": match.get("start", 0),
                    "end_pos": match.get("end", 0),
                    "context": json.dumps(match.get("context", [])),
                }
                for match in match_data.get("matches", [])
            )

        return matched_files_data, matches_data

    def _insert_matched_files(self, cursor: sqlite3.Cursor, matched_files_data: list[dict]) -> list[int]:
        """插入匹配文件并返回其 ID.

        Args:
            cursor: SQLite 游标
            matched_files_data: 匹配文件数据列表

        Returns
        -------
            已插入的文件 ID 列表
        """
        file_ids = []
        if not matched_files_data:
            return file_ids

        columns = list(matched_files_data[0].keys())
        placeholders = ", ".join(["?" for _ in columns])
        query = f"INSERT INTO matched_files ({', '.join(columns)}) VALUES ({placeholders})"

        # 逐条插入以获取每个文件的 ID
        for data in matched_files_data:
            values = [data[col] for col in columns]
            cursor.execute(query, values)
            file_ids.append(cursor.lastrowid)

        return file_ids

    def _insert_matches(self, cursor: sqlite3.Cursor, matches_data: list[dict], file_ids: list[int]) -> None:
        """插入匹配详情并分配文件 ID.

        Args:
            cursor: SQLite 游标
            matches_data: 匹配数据列表
            file_ids: 要分配的文件 ID 列表
        """
        if not matches_data or not file_ids:
            return

        matches_with_file_id = []
        total_matches = len(matches_data)
        total_files = len(file_ids)

        for idx, match_entry in enumerate(matches_data):
            # 平均分配或按文件顺序分配
            file_idx = min(idx * total_files // total_matches, total_files - 1) if total_matches > 0 else 0
            match_entry_copy = match_entry.copy()
            match_entry_copy["file_id"] = file_ids[file_idx]
            matches_with_file_id.append(match_entry_copy)

        # 批量插入匹配详情
        if matches_with_file_id:
            columns = ["file_id", *list(matches_data[0].keys())]
            placeholders = ", ".join(["?" for _ in columns])
            query = f"INSERT INTO matches ({', '.join(columns)}) VALUES ({placeholders})"
            values = [[data[col] for col in columns] for data in matches_with_file_id]
            cursor.executemany(query, values)

    def save_scan_results(self, results: dict[str, Any], session_name: str | None = None) -> int:
        """将扫描结果保存到数据库.

        Args:
            results: 扫描结果字典
            session_name: 可选的自定义会话名称

        Returns
        -------
            保存的扫描会话 ID
        """
        try:
            with self.transaction() as conn:
                cursor = conn.cursor()

                # 插入扫描会话记录
                session_data = self._prepare_session_data(results, session_name)
                cursor.execute(
                    """
                    INSERT INTO scan_sessions (
                        session_name, input_directory, scan_time,
                        total_files, files_with_matches, rules_count,
                        file_types_scanned, use_pdf_ocr, use_process_pool,
                        duration_seconds, created_at
                    ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
                """,
                    tuple(session_data.values()),
                )

                session_id = cursor.lastrowid
                if session_id is None:
                    msg = "无法获取会话 ID"
                    raise RuntimeError(msg)

                # 准备匹配数据
                matched_files_data, matches_data = self._prepare_match_data(results, session_id)

                # 插入匹配文件并获取 ID
                file_ids = self._insert_matched_files(cursor, matched_files_data)

                # 插入匹配详情
                self._insert_matches(cursor, matches_data, file_ids)

                return session_id

        except sqlite3.Error as e:
            msg = f"保存扫描结果失败：{e}"
            raise RuntimeError(msg) from e

    def get_scan_sessions(self, limit: int = 50) -> list[dict[str, Any]]:
        """获取扫描会话列表.

        Args:
            limit: 返回的最大会话数

        Returns
        -------
            扫描会话字典列表
        """
        return self.query(
            """
            SELECT * FROM scan_sessions
            ORDER BY created_at DESC
            LIMIT ?
        """,
            (limit,),
        )

    def get_session_results(self, session_id: int) -> dict[str, Any]:
        """获取会话的完整扫描结果.

        Args:
            session_id: 会话 ID

        Returns
        -------
            扫描结果字典
        """
        # 获取会话信息
        session = self.query_one("SELECT * FROM scan_sessions WHERE id = ?", (session_id,))

        if not session:
            return {}

        # 获取匹配的文件
        matched_files_rows = self.query("SELECT * FROM matched_files WHERE session_id = ?", (session_id,))

        matched_files = []
        for file_dict in matched_files_rows:
            file_id = file_dict["id"]

            # 获取匹配详情
            matches = self.query("SELECT * FROM matches WHERE file_id = ?", (file_id,))

            # 转换数据格式
            match_data = {
                "file_path": file_dict["file_path"],
                "file_type": file_dict["file_type"],
                "file_size": file_dict["file_size"],
                "metadata": json.loads(file_dict["metadata"]) if file_dict["metadata"] else {},
                "matches": [],
            }

            for match in matches:
                match_data["matches"].append({
                    "rule_name": match["rule_name"],
                    "rule_description": match["rule_description"],
                    "type": match["match_type"],
                    "match": match["match_text"],
                    "line_number": match["line_number"],
                    "start": match["start_pos"],
                    "end": match["end_pos"],
                    "context": json.loads(match["context"]) if match["context"] else [],
                })

            matched_files.append(match_data)

        # 重构为原始结果格式
        return {
            "scan_info": {
                "input_directory": session["input_directory"],
                "scan_time": session["scan_time"],
                "total_files": session["total_files"],
                "files_with_matches": session["files_with_matches"],
                "rules_count": session["rules_count"],
                "file_types_scanned": json.loads(session["file_types_scanned"]),
                "use_pdf_ocr": bool(session["use_pdf_ocr"]),
                "use_process_pool": bool(session["use_process_pool"]),
                "duration_seconds": session["duration_seconds"],
            },
            "matches": matched_files,
        }

    def delete_session(self, session_id: int) -> bool:
        """删除扫描会话及所有相关数据.

        Args:
            session_id: 要删除的会话 ID

        Returns
        -------
            成功删除返回 True
        """
        try:
            rows_affected = self.delete("scan_sessions", "id = ?", (session_id,))
        except sqlite3.Error:
            return False
        else:
            return rows_affected > 0

    def search_matches(self, rule_name: str | None = None, file_pattern: str | None = None) -> list[dict[str, Any]]:
        """在所有扫描会话中搜索.

        Args:
            rule_name: 按规则名称筛选
            file_pattern: 按文件路径模式筛选

        Returns
        -------
            匹配结果列表
        """
        query = """
            SELECT m.*, mf.file_path, mf.file_type, ss.session_name, ss.scan_time
            FROM matches m
            JOIN matched_files mf ON m.file_id = mf.id
            JOIN scan_sessions ss ON mf.session_id = ss.id
            WHERE 1=1
        """
        params = []

        if rule_name:
            query += " AND m.rule_name = ?"
            params.append(rule_name)

        if file_pattern:
            query += " AND mf.file_path LIKE ?"
            params.append(f"%{file_pattern}%")

        query += " ORDER BY ss.created_at DESC"

        return self.query(query, tuple(params))

    def get_statistics(self) -> dict[str, Any]:
        """获取总体统计信息.

        Returns
        -------
            统计信息字典
        """
        stats = {}

        # 总会话数
        stats["total_sessions"] = self.count("scan_sessions")

        # 总匹配文件数
        stats["total_matched_files"] = self.count("matched_files")

        # 总匹配项数
        stats["total_matches"] = self.count("matches")

        # 最近扫描时间
        result = self.query_one("SELECT MAX(created_at) as last_scan FROM scan_sessions")
        stats["last_scan_time"] = result["last_scan"] if result else None

        return stats

    def has_scan_sessions(self) -> bool:
        """检查数据库中是否有任何扫描会话.

        Returns
        -------
            有扫描会话返回 True，否则返回 False
        """
        return self.count("scan_sessions") > 0

    def _close(self) -> None:
        """关闭数据库连接并释放资源.

        此方法是为了与现有代码兼容而提供。
        它调用父类的 close() 方法。
        """
        self.close()

    def count(self, table: str, where: str | None = None, where_params: tuple | None = None) -> int:
        """计算表中的记录数.

        此方法重写父类方法以提供更简单的接口.

        Args:
            table: 表名
            where: 可选的 WHERE 子句（不含'WHERE'关键字）
            where_params: 可选的 WHERE 子句参数

        Returns
        -------
            符合条件的记录数
        """
        if where:
            query = f"SELECT COUNT(*) FROM {table} WHERE {where}"
            params = where_params or ()
            result = self.fetch_one(query, params)
        else:
            query = f"SELECT COUNT(*) FROM {table}"
            result = self.fetch_one(query)

        return result["COUNT(*)"] if result else 0

    def query(self, query: str, params: tuple | None = None) -> list[dict]:
        """执行 SELECT 查询并返回结果.

        为了向后兼容而提供的便捷方法，封装了 fetch_all.

        Args:
            query: SQL SELECT 查询
            params: 查询参数

        Returns
        -------
            结果字典列表
        """
        return self.fetch_all(query, params)

    def query_one(self, query: str, params: tuple | None = None) -> dict | None:
        """执行 SELECT 查询并返回单个结果.

        为了向后兼容而提供的便捷方法，封装了 fetch_one.

        Args:
            query: SQL SELECT 查询
            params: 查询参数

        Returns
        -------
            单个结果字典，如果没有结果则返回 None
        """
        return self.fetch_one(query, params)

    def delete(self, table: str, where: str, where_params: tuple | None = None) -> int:
        """从表中删除记录.

        此方法重写父类方法以确保正确的错误处理.

        Args:
            table: 表名
            where: WHERE 子句（不含'WHERE'关键字）
            where_params: WHERE 子句参数

        Returns
        -------
            受影响的行数

        Raises
        ------
            RuntimeError: 如果删除失败
        """
        try:
            return super().delete(table, where, where_params)
        except Exception as e:
            msg = f"删除记录失败：{e}"
            raise RuntimeError(msg) from e
