#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""Oracle 异步处理器（oracledb async pool）"""
import logging
import time
from typing import Any, Dict, List, Optional

from core.database_manager.handlers.common import (
    format_connection_error,
    format_size,
    log_database_connect_failure,
    serialize_row,
)
from core.database_manager.handlers.pools import get_oracledb_pool
from core.database_manager.handlers.transaction_mixin import HandlerTransactionMixin
from core.database_manager.sql_utils import quote_identifier, quote_table, split_sql_statements

logger = logging.getLogger(__name__)

_ORACLE_SYSTEM_SCHEMAS = (
    "SYS",
    "SYSTEM",
    "OUTLN",
    "XDB",
    "CTXSYS",
    "MDSYS",
    "ORDDATA",
    "ORDSYS",
    "LBACSYS",
    "DBSNMP",
    "APPQOSSYS",
    "AUDSYS",
    "GSMADMIN_INTERNAL",
    "OJVMSYS",
    "ORDPLUGINS",
    "SI_INFORMTN_SCHEMA",
    "WMSYS",
)


class OracleHandler(HandlerTransactionMixin):
    """Oracle 异步处理器"""

    def __init__(
        self,
        host: str,
        port: int,
        user: str,
        password: str,
        database: str,
        extra_options: Optional[Dict[str, Any]] = None,
    ):
        self.host = host
        self.port = port
        self.user = user
        self.password = password
        self.database = database
        self.extra_options = extra_options or {}
        self.conn = None
        self._pool = None
        self._last_connect_error: Optional[str] = None
        self._in_transaction = False

    @property
    def default_schema(self) -> str:
        return (self.user or "").upper()

    async def _release_connection(self) -> None:
        if self.conn and self._pool:
            try:
                await self._pool.release(self.conn)
            except Exception as exc:
                logger.warning("Error releasing Oracle connection: %s", exc)
        self.conn = None
        self._pool = None

    async def connect(self, database: str = None) -> bool:
        _ = database  # Oracle 使用 service name，不按 PG 方式切换 database
        try:
            await self._release_connection()
            pool = await get_oracledb_pool(
                self.host,
                self.port,
                self.user,
                self.password,
                self.database,
                self.extra_options,
            )
            self._pool = pool
            self.conn = await pool.acquire()
            self._last_connect_error = None
            return True
        except Exception as e:
            self._last_connect_error = format_connection_error(e)
            log_database_connect_failure(
                db_type="oracle",
                host=self.host,
                port=self.port,
                user=self.user,
                database=self.database,
                detail=self._last_connect_error,
            )
            self.conn = None
            self._pool = None
            return False

    async def close(self):
        await self._release_connection()

    async def _execute_query(self, query: str, params: dict = None) -> List[Dict[str, Any]]:
        async with self.conn.cursor() as cursor:
            await cursor.execute(query, params or {})
            if not cursor.description:
                return []
            columns = [col[0].lower() for col in cursor.description]
            rows = await cursor.fetchall()
            return [dict(zip(columns, row)) for row in rows]

    async def _execute_command(self, command: str, params: dict = None) -> int:
        async with self.conn.cursor() as cursor:
            await cursor.execute(command, params or {})
            return cursor.rowcount

    async def _run_sql_query(self, sql: str) -> list:
        return await self._execute_query(sql)

    async def _run_sql_command(self, sql: str) -> int:
        return await self._execute_command(sql)

    async def begin_transaction(self) -> bool:
        if self._in_transaction:
            return True
        if not await self.connect():
            return False
        self._in_transaction = True
        return True

    async def commit_transaction(self) -> None:
        if not self._in_transaction:
            return
        try:
            await self.conn.commit()
        finally:
            self._in_transaction = False
            await self.close()

    async def rollback_transaction(self) -> None:
        if not self._in_transaction:
            return
        try:
            await self.conn.rollback()
        finally:
            self._in_transaction = False
            await self.close()

    async def get_databases(self) -> List[Dict[str, Any]]:
        """Oracle 返回虚拟 database（service name）"""
        try:
            if not await self.connect():
                return []
            service_name = self.database or self.default_schema or "ORCL"
            count_rows = await self._execute_query(
                "SELECT COUNT(*) AS tables_count FROM user_tables"
            )
            tables_count = count_rows[0]["tables_count"] if count_rows else 0
            return [
                {
                    "name": service_name,
                    "owner": self.user,
                    "encoding": "UTF-8",
                    "collation": None,
                    "size": "0 bytes",
                    "size_bytes": 0,
                    "description": "Oracle service instance",
                    "tables_count": tables_count,
                }
            ]
        except Exception as e:
            logger.error("Failed to get databases: %s", e)
            return []
        finally:
            await self.close()

    async def create_database(self, name: str, **kwargs) -> bool:
        raise NotImplementedError("Oracle 不支持创建 Database")

    async def drop_database(self, name: str) -> bool:
        raise NotImplementedError("Oracle 不支持删除 Database")

    async def rename_database(self, name: str, new_name: str) -> bool:
        raise NotImplementedError("Oracle 不支持重命名 Database")

    async def create_schema(self, name: str, database: str = None, owner: str = None) -> bool:
        raise NotImplementedError("Oracle Schema 创建需要 DBA 权限，暂不支持")

    async def drop_schema(self, name: str, database: str = None, cascade: bool = True) -> bool:
        raise NotImplementedError("Oracle Schema 删除需要 DBA 权限，暂不支持")

    async def rename_schema(self, name: str, new_name: str, database: str = None) -> bool:
        raise NotImplementedError("Oracle 不支持重命名 Schema")

    async def get_schemas(self, database: str = None) -> List[Dict[str, Any]]:
        try:
            if not await self.connect(database):
                return []
            placeholders = ", ".join([f":s{i}" for i in range(len(_ORACLE_SYSTEM_SCHEMAS))])
            params = {f"s{i}": schema for i, schema in enumerate(_ORACLE_SYSTEM_SCHEMAS)}
            query = f"""
                SELECT username AS name,
                       username AS owner,
                       (SELECT COUNT(*)
                        FROM all_tables t
                        WHERE t.owner = u.username) AS tables_count
                FROM all_users u
                WHERE u.username NOT IN ({placeholders})
                ORDER BY u.username
            """
            return await self._execute_query(query, params)
        except Exception as e:
            logger.error("Failed to get schemas: %s", e)
            return []
        finally:
            await self.close()

    async def get_tables(
        self, database: str = None, schema_name: str = None
    ) -> List[Dict[str, Any]]:
        try:
            if not await self.connect(database):
                return []
            schema = (schema_name or self.default_schema).upper()
            query = """
                SELECT owner AS schema_name,
                       table_name,
                       'BASE TABLE' AS table_type,
                       num_rows AS row_count,
                       0 AS total_size_bytes
                FROM all_tables
                WHERE owner = :owner
                ORDER BY table_name
            """
            tables = await self._execute_query(query, {"owner": schema})
            for table in tables:
                table["total_size"] = format_size(0)
            return tables
        except Exception as e:
            logger.error("Failed to get tables: %s", e)
            return []
        finally:
            await self.close()

    async def get_table_columns(
        self, table_name: str, schema_name: str = None, database: str = None
    ) -> List[Dict[str, Any]]:
        try:
            if not await self.connect(database):
                return []
            schema = (schema_name or self.default_schema).upper()
            query = """
                SELECT c.column_name,
                       c.data_type,
                       CASE WHEN c.nullable = 'Y' THEN 1 ELSE 0 END AS is_nullable,
                       c.data_default AS column_default,
                       c.char_length AS character_maximum_length,
                       c.data_precision AS numeric_precision,
                       c.data_scale AS numeric_scale,
                       c.column_id AS ordinal_position,
                       CASE WHEN pk.column_name IS NOT NULL THEN 1 ELSE 0 END AS is_primary_key,
                       CASE WHEN uq.column_name IS NOT NULL THEN 1 ELSE 0 END AS is_unique,
                       cc.comments AS description
                FROM all_tab_columns c
                LEFT JOIN (
                    SELECT cols.column_name, cols.owner, cols.table_name
                    FROM all_constraints cons
                    JOIN all_cons_columns cols
                      ON cons.constraint_name = cols.constraint_name
                     AND cons.owner = cols.owner
                    WHERE cons.constraint_type = 'P'
                ) pk ON pk.owner = c.owner
                    AND pk.table_name = c.table_name
                    AND pk.column_name = c.column_name
                LEFT JOIN (
                    SELECT cols.column_name, cols.owner, cols.table_name
                    FROM all_constraints cons
                    JOIN all_cons_columns cols
                      ON cons.constraint_name = cols.constraint_name
                     AND cons.owner = cols.owner
                    WHERE cons.constraint_type = 'U'
                ) uq ON uq.owner = c.owner
                    AND uq.table_name = c.table_name
                    AND uq.column_name = c.column_name
                LEFT JOIN all_col_comments cc
                  ON cc.owner = c.owner
                 AND cc.table_name = c.table_name
                 AND cc.column_name = c.column_name
                WHERE c.owner = :owner AND c.table_name = :table_name
                ORDER BY c.column_id
            """
            return await self._execute_query(
                query, {"owner": schema, "table_name": table_name.upper()}
            )
        except Exception as e:
            logger.error("Failed to get table columns: %s", e)
            return []
        finally:
            await self.close()

    async def get_table_indexes(
        self, table_name: str, schema_name: str = None, database: str = None
    ) -> List[Dict[str, Any]]:
        try:
            if not await self.connect(database):
                return []
            schema = (schema_name or self.default_schema).upper()
            query = """
                SELECT i.index_name,
                       i.index_type,
                       LISTAGG(c.column_name, ', ') WITHIN GROUP (ORDER BY c.column_position) AS columns,
                       CASE WHEN i.uniqueness = 'UNIQUE' THEN 1 ELSE 0 END AS is_unique,
                       CASE WHEN i.index_name IN (
                           SELECT constraint_name FROM all_constraints
                           WHERE owner = :owner AND table_name = :table_name
                             AND constraint_type = 'P'
                       ) THEN 1 ELSE 0 END AS is_primary,
                       '' AS definition
                FROM all_indexes i
                JOIN all_ind_columns c
                  ON i.owner = c.index_owner
                 AND i.index_name = c.index_name
                WHERE i.table_owner = :owner AND i.table_name = :table_name
                GROUP BY i.index_name, i.index_type, i.uniqueness
                ORDER BY i.index_name
            """
            indexes = await self._execute_query(
                query, {"owner": schema, "table_name": table_name.upper()}
            )
            for idx in indexes:
                unique = "UNIQUE " if idx.get("is_unique") else ""
                idx["definition"] = (
                    f"CREATE {unique}INDEX {idx['index_name']} "
                    f"ON {schema}.{table_name.upper()} ({idx.get('columns', '')})"
                )
            return indexes
        except Exception as e:
            logger.error("Failed to get table indexes: %s", e)
            return []
        finally:
            await self.close()

    async def get_table_constraints(
        self, table_name: str, schema_name: str = None, database: str = None
    ) -> List[Dict[str, Any]]:
        try:
            if not await self.connect(database):
                return []
            schema = (schema_name or self.default_schema).upper()
            query = """
                SELECT c.constraint_name,
                       c.constraint_type,
                       LISTAGG(cc.column_name, ', ') WITHIN GROUP (ORDER BY cc.position) AS columns,
                       ref.table_name AS referenced_table,
                       LISTAGG(ref_cc.column_name, ', ') WITHIN GROUP (ORDER BY ref_cc.position)
                           AS referenced_columns,
                       c.search_condition AS definition
                FROM all_constraints c
                LEFT JOIN all_cons_columns cc
                  ON c.owner = cc.owner
                 AND c.constraint_name = cc.constraint_name
                LEFT JOIN all_constraints ref
                  ON c.r_owner = ref.owner
                 AND c.r_constraint_name = ref.constraint_name
                LEFT JOIN all_cons_columns ref_cc
                  ON ref.owner = ref_cc.owner
                 AND ref.constraint_name = ref_cc.constraint_name
                WHERE c.owner = :owner AND c.table_name = :table_name
                GROUP BY c.constraint_name, c.constraint_type, ref.table_name, c.search_condition
                ORDER BY c.constraint_type, c.constraint_name
            """
            return await self._execute_query(
                query, {"owner": schema, "table_name": table_name.upper()}
            )
        except Exception as e:
            logger.error("Failed to get table constraints: %s", e)
            return []
        finally:
            await self.close()

    async def get_table_structure(
        self, table_name: str, database: str = None, schema_name: str = None
    ) -> Dict[str, Any]:
        schema = schema_name or self.default_schema
        tables = await self.get_tables(database=database, schema_name=schema)
        table_info = next(
            (t for t in tables if t["table_name"].upper() == table_name.upper()),
            None,
        )
        if not table_info:
            raise ValueError(f"Table {schema}.{table_name} not found")
        columns = await self.get_table_columns(table_name, schema, database)
        indexes = await self.get_table_indexes(table_name, schema, database)
        constraints = await self.get_table_constraints(table_name, schema, database)
        return {
            "table_info": table_info,
            "columns": columns,
            "indexes": indexes,
            "constraints": constraints,
        }

    async def get_table_ddl(
        self, table_name: str, schema_name: str = None, database: str = None
    ) -> str:
        try:
            schema = (schema_name or self.default_schema).upper()
            columns = await self.get_table_columns(table_name, schema, database)
            if not columns:
                return f"-- 无法获取表 {schema}.{table_name} 的DDL"
            full_name = quote_table(schema, table_name.upper(), "oracle")
            ddl_lines = [f"CREATE TABLE {full_name} ("]
            col_defs = []
            for col in columns:
                col_def = f"    {quote_identifier(col['column_name'], 'oracle')} {col['data_type']}"
                if col.get("character_maximum_length"):
                    col_def += f"({col['character_maximum_length']})"
                elif col.get("numeric_precision"):
                    scale = col.get("numeric_scale")
                    col_def += f"({col['numeric_precision']}"
                    col_def += f",{scale})" if scale is not None else ")"
                if not col.get("is_nullable"):
                    col_def += " NOT NULL"
                col_defs.append(col_def)
            ddl_lines.append(",\n".join(col_defs))
            ddl_lines.append(");")
            return "\n".join(ddl_lines)
        except Exception as e:
            return f"-- 获取DDL失败: {str(e)}"

    async def get_views(
        self, database: str = None, schema_name: str = None
    ) -> List[Dict[str, Any]]:
        try:
            if not await self.connect(database):
                return []
            schema = (schema_name or self.default_schema).upper()
            query = """
                SELECT view_name,
                       owner AS schema_name,
                       text AS view_definition,
                       0 AS is_updatable,
                       NULL AS check_option,
                       'VIEW' AS view_type
                FROM all_views
                WHERE owner = :owner
                ORDER BY view_name
            """
            return await self._execute_query(query, {"owner": schema})
        except Exception as e:
            logger.error("Failed to get views: %s", e)
            return []
        finally:
            await self.close()

    async def get_view_structure(self, view_name: str, schema_name: str = None) -> Dict[str, Any]:
        try:
            if not await self.connect():
                raise ValueError("Failed to connect")
            schema = (schema_name or self.default_schema).upper()
            view_query = """
                SELECT view_name, owner AS schema_name, text AS view_definition,
                       0 AS is_updatable, NULL AS check_option, 'VIEW' AS view_type
                FROM all_views
                WHERE owner = :owner AND view_name = :view_name
            """
            view_info = await self._execute_query(
                view_query, {"owner": schema, "view_name": view_name.upper()}
            )
            if not view_info:
                raise ValueError(f"View {schema}.{view_name} not found")
            view_info = view_info[0]
            columns = await self.get_table_columns(view_name, schema)
            definition_sql = await self.get_view_definition(view_name, schema)
            dependencies = await self.get_view_dependencies(view_name, schema)
            return {
                "view_info": view_info,
                "columns": columns,
                "dependencies": dependencies,
                "definition_sql": definition_sql,
            }
        except Exception as e:
            logger.error("Failed to get view structure: %s", e)
            raise
        finally:
            await self.close()

    async def get_view_definition(self, view_name: str, schema_name: str = None) -> str:
        try:
            if not await self.connect():
                return "-- 无法连接数据库"
            schema = (schema_name or self.default_schema).upper()
            rows = await self._execute_query(
                """
                SELECT text AS definition
                FROM all_views
                WHERE owner = :owner AND view_name = :view_name
                """,
                {"owner": schema, "view_name": view_name.upper()},
            )
            if rows and rows[0].get("definition"):
                full = quote_table(schema, view_name.upper(), "oracle")
                return f"CREATE OR REPLACE VIEW {full} AS\n{rows[0]['definition']}"
            return f"-- 无法获取视图 {schema}.{view_name} 的定义"
        except Exception as e:
            return f"-- 获取视图定义失败: {str(e)}"
        finally:
            await self.close()

    async def get_view_dependencies(self, view_name: str, schema_name: str = None) -> List[str]:
        try:
            if not await self.connect():
                return []
            schema = (schema_name or self.default_schema).upper()
            rows = await self._execute_query(
                """
                SELECT DISTINCT referenced_name AS table_name
                FROM all_dependencies
                WHERE owner = :owner
                  AND name = :view_name
                  AND type = 'VIEW'
                  AND referenced_type IN ('TABLE', 'VIEW')
                ORDER BY referenced_name
                """,
                {"owner": schema, "view_name": view_name.upper()},
            )
            return [row["table_name"] for row in rows]
        except Exception as e:
            logger.error("Failed to get view dependencies: %s", e)
            return []
        finally:
            await self.close()

    async def query_data(
        self,
        table_name: str,
        schema_name: str = None,
        page: int = 1,
        page_size: int = 20,
        where: str = None,
        order_by: str = None,
        database: str = None,
    ) -> Dict[str, Any]:
        try:
            if not await self.connect(database):
                return {"columns": [], "rows": [], "total": 0, "page": page, "page_size": page_size}
            schema = (schema_name or self.default_schema).upper()
            full_table = quote_table(schema, table_name.upper(), "oracle")
            count_q = f"SELECT COUNT(*) AS total FROM {full_table}"
            if where:
                count_q += f" WHERE {where}"
            count_result = await self._execute_query(count_q)
            total = count_result[0]["total"] if count_result else 0
            offset = (page - 1) * page_size
            data_q = f"SELECT * FROM {full_table}"
            if where:
                data_q += f" WHERE {where}"
            if order_by:
                data_q += f" ORDER BY {order_by}"
            data_q += f" OFFSET {offset} ROWS FETCH NEXT {page_size} ROWS ONLY"
            rows = await self._execute_query(data_q)
            rows_list = [serialize_row(row) for row in rows]
            columns = list(rows_list[0].keys()) if rows_list else []
            return {
                "columns": columns,
                "rows": rows_list,
                "total": total,
                "page": page,
                "page_size": page_size,
            }
        except Exception as e:
            logger.error("Failed to query data: %s", e)
            return {"columns": [], "rows": [], "total": 0, "page": page, "page_size": page_size}
        finally:
            await self.close()

    async def insert_data(
        self, table_name: str, data: Dict[str, Any], schema_name: str = None
    ) -> Dict[str, Any]:
        try:
            if not await self.connect():
                return {"success": False, "message": "数据库连接失败", "affected_rows": 0}
            schema = (schema_name or self.default_schema).upper()
            full_table = quote_table(schema, table_name.upper(), "oracle")
            columns = list(data.keys())
            quoted_columns = ", ".join(
                quote_identifier(col, "oracle") for col in columns
            )
            placeholders = ", ".join([f":v{i}" for i in range(len(columns))])
            params = {f"v{i}": v for i, v in enumerate(data.values())}
            query = f"INSERT INTO {full_table} ({quoted_columns}) VALUES ({placeholders})"
            affected_rows = await self._execute_command(query, params)
            return {"success": True, "message": "插入成功", "affected_rows": affected_rows or 1}
        except Exception as e:
            return {"success": False, "message": str(e), "affected_rows": 0}
        finally:
            await self.close()

    async def update_data(
        self, table_name: str, data: Dict[str, Any], where: str, schema_name: str = None
    ) -> Dict[str, Any]:
        try:
            if not await self.connect():
                return {"success": False, "message": "数据库连接失败", "affected_rows": 0}
            schema = (schema_name or self.default_schema).upper()
            full_table = quote_table(schema, table_name.upper(), "oracle")
            set_parts = []
            params = {}
            for i, (k, v) in enumerate(data.items()):
                key = f"s{i}"
                set_parts.append(f"{quote_identifier(k, 'oracle')} = :{key}")
                params[key] = v
            query = f"UPDATE {full_table} SET {', '.join(set_parts)} WHERE {where}"
            affected_rows = await self._execute_command(query, params)
            return {
                "success": True,
                "message": f"更新成功，影响 {affected_rows} 行",
                "affected_rows": affected_rows,
            }
        except Exception as e:
            return {"success": False, "message": str(e), "affected_rows": 0}
        finally:
            await self.close()

    async def delete_data(
        self, table_name: str, where: str, schema_name: str = None
    ) -> Dict[str, Any]:
        try:
            if not await self.connect():
                return {"success": False, "message": "数据库连接失败", "affected_rows": 0}
            schema = (schema_name or self.default_schema).upper()
            full_table = quote_table(schema, table_name.upper(), "oracle")
            query = f"DELETE FROM {full_table} WHERE {where}"
            affected_rows = await self._execute_command(query)
            return {
                "success": True,
                "message": f"删除成功，影响 {affected_rows} 行",
                "affected_rows": affected_rows,
            }
        except Exception as e:
            return {"success": False, "message": str(e), "affected_rows": 0}
        finally:
            await self.close()

    async def execute_ddl(
        self, sql: str, database: str = None, schema_name: str = None
    ) -> Dict[str, Any]:
        try:
            if not await self.connect(database):
                return {"success": False, "message": "数据库连接失败", "affected_rows": 0}
            statements = split_sql_statements(sql)
            if not statements:
                return {"success": False, "message": "无有效SQL语句", "affected_rows": 0}
            for index, statement in enumerate(statements, start=1):
                try:
                    await self._execute_command(statement)
                except Exception as exc:
                    return {
                        "success": False,
                        "message": f"DDL执行失败 (第{index}条): {exc}",
                        "affected_rows": 0,
                    }
            return {"success": True, "message": "DDL执行成功", "affected_rows": 0}
        except Exception as e:
            return {"success": False, "message": f"DDL执行失败: {str(e)}", "affected_rows": 0}
        finally:
            await self.close()
