from typing import ClassVar, Optional, get_type_hints

from pydantic import BaseModel

from evoseer_utils.db_connection import DbConnection
from evoseer_utils.mutations import Mutation


class OutputDescription(BaseModel):
    mutation: Mutation
    version: ClassVar[str]

    _table_name: ClassVar[str]
    db_fields: ClassVar[list[str]]

    @property
    def table_name(self) -> str:
        # we do that because it allows automatic views based on "annot_" prefix
        return "annot_" + self._table_name

    @classmethod
    def _get_all_db_fields(cls) -> list[str]:
        # Always include version automatically
        return ["version"] + cls.db_fields

    @classmethod
    def _python_type_to_sql(cls, python_type: type) -> str:
        type_map = {
            int: "INTEGER",
            float: "REAL",
            str: "TEXT",
            bool: "INTEGER",
        }
        # Handle Optional types
        origin = getattr(python_type, "__origin__", None)
        if origin is type(None) or str(python_type).startswith("typing.Union"):
            args = getattr(python_type, "__args__", ())
            if args:
                python_type = args[0] if args[0] is not type(None) else args[1]

        return type_map.get(python_type, "TEXT")

    @classmethod
    def _ensure_table_exists(cls, table_name: str) -> None:
        conn = DbConnection.get_connection()
        cursor = conn.cursor()

        type_hints = get_type_hints(cls)
        columns = ["id INTEGER PRIMARY KEY AUTOINCREMENT", "mutation_id INTEGER NOT NULL UNIQUE"]

        for field_name in cls._get_all_db_fields():
            field_type = type_hints.get(field_name, str)
            sql_type = cls._python_type_to_sql(field_type)
            columns.append(f"{field_name} {sql_type}")

        columns.append("FOREIGN KEY (mutation_id) REFERENCES mutations(id) ON DELETE CASCADE")

        cursor.execute(f"""
            CREATE TABLE IF NOT EXISTS {table_name} (
                {', '.join(columns)}
            )
        """)

        cursor.execute(f"""
            CREATE INDEX IF NOT EXISTS idx_{table_name}_mutation
            ON {table_name}(mutation_id)
        """)

        conn.commit()

    def insert_to_db(self, table_name: Optional[str] = None) -> None:
        if table_name is None:
            table_name = self.table_name

        self._ensure_table_exists(table_name)
        self.mutation.ensure_in_db()

        conn = DbConnection.get_connection()
        cursor = conn.cursor()

        all_fields = self._get_all_db_fields()
        fields = ["mutation_id"] + all_fields
        values = [self.mutation.id] + [getattr(self, field) for field in all_fields]

        placeholders = ", ".join(["?"] * len(values))

        cursor.execute(
            f"""
            INSERT OR REPLACE INTO {table_name}
            ({', '.join(fields)})
            VALUES ({placeholders})
        """,
            values,
        )

        conn.commit()

    @classmethod
    def insert_batch_to_db(
        cls, outputs: list["OutputDescription"], table_name: Optional[str] = None
    ) -> None:
        if table_name is None:
            table_name = cls.table_name

        cls._ensure_table_exists(table_name)

        mutations = [output.mutation for output in outputs]
        Mutation.ensure_in_db_batch(mutations)

        conn = DbConnection.get_connection()
        cursor = conn.cursor()

        all_fields = cls._get_all_db_fields()
        fields = ["mutation_id"] + all_fields
        placeholders = ", ".join(["?"] * len(fields))

        values_list = []
        for output in outputs:
            values = [output.mutation.id] + [getattr(output, field) for field in all_fields]
            values_list.append(values)

        cursor.executemany(
            f"""
            INSERT OR REPLACE INTO {table_name}
            ({', '.join(fields)})
            VALUES ({placeholders})
        """,
            values_list,
        )

        conn.commit()
