"""Database models and schemas for LSCSIM."""

from __future__ import annotations

from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Any

from sqlalchemy import Boolean, Column, DateTime, Float, Integer, String, Text, create_engine
from sqlalchemy.engine import reflection
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker

from pytola_sim.lscsim.utils.logger import get_logger


# Create a separate Base for each instance to avoid conflicts
def create_base() -> type:
    """Create a separate Base for each instance to avoid conflicts."""
    return declarative_base()


# Keep global Base for backward compatibility
Base = declarative_base()


@dataclass
class ColumnDefinition:
    """Definition of database column."""

    name: str
    data_type: str
    nullable: bool = True
    primary_key: bool = False
    unique: bool = False
    default_value: object = None
    description: str = ""

    def to_sqlalchemy_column(self) -> Column:
        """Convert to SQLAlchemy Column."""
        type_mapping = {
            "integer": Integer,
            "string": String,
            "text": Text,
            "float": Float,
            "boolean": Boolean,
            "datetime": DateTime,
        }

        column_type = type_mapping.get(self.data_type.lower(), String)
        return Column(
            self.name,
            column_type,
            nullable=self.nullable,
            primary_key=self.primary_key,
            unique=self.unique,
            default=self.default_value,
        )


class DatabaseManager:
    """Manager for database operations."""

    def __init__(self, database_url: str = "sqlite:///lscsim.db") -> None:
        self.database_url = database_url
        self.engine = create_engine(database_url)
        self.Session = sessionmaker(bind=self.engine)
        self.Base = create_base()  # Create separate Base for this instance
        self._tables: dict[str, Any] = {}
        logger = get_logger(__name__)
        logger.info(f"Database manager initialized with {database_url}")

    def create_table(self, table_name: str, columns: list[ColumnDefinition]) -> bool:
        """Create a new table with specified columns."""
        logger = get_logger(__name__)
        try:
            # Create dynamic table class
            attrs: dict[str, Any] = {"__tablename__": table_name}

            # Add columns
            for column_def in columns:
                attrs[column_def.name] = column_def.to_sqlalchemy_column()

            # Add base columns
            attrs["id"] = Column(Integer, primary_key=True)
            attrs["created_at"] = Column(DateTime, default=datetime.now)
            attrs["updated_at"] = Column(
                DateTime,
                default=datetime.now,
                onupdate=datetime.now,
            )

            # Check if table already exists in this instance's Base
            if hasattr(self.Base, "_decl_class_registry") and table_name.title() in self.Base._decl_class_registry:  # type: ignore[operator]
                # Table class already exists, use existing
                table_class = self.Base._decl_class_registry[table_name.title()]  # type: ignore[index]
            else:
                # Create new table class
                table_class = type(table_name.title(), (self.Base,), attrs)

            self._tables[table_name] = table_class

            # Create table in database with extend_existing=True
            inspector = reflection.Inspector.from_engine(self.engine)
            if table_name not in inspector.get_table_names():
                table_class.__table__.create(self.engine)
            else:
                # Table exists, ensure it can be redefined
                table_class.__table__.extend_existing = True

            logger.info(f"Created table: {table_name}")
        except (OSError, RuntimeError, ValueError):
            logger.exception(f"Failed to create table {table_name}")
            return False
        else:
            return True

    def insert_record(self, table_name: str, data: dict[str, Any]) -> int | None:
        """Insert a new record into table."""
        logger = get_logger(__name__)
        try:
            if table_name not in self._tables:
                logger.error(f"Table not found: {table_name}")
                return None

            table_class = self._tables[table_name]
            session = self.Session()

            try:
                record = table_class(**data)
                session.add(record)
                session.commit()
                record_id = record.id
                logger.info(f"Inserted record into {table_name} with ID: {record_id}")
                return record_id
            finally:
                session.close()

        except (OSError, RuntimeError, ValueError):
            logger.exception(f"Failed to insert record into {table_name}")
            return None

    def query_records(
        self,
        table_name: str,
        filters: dict[str, Any] | None = None,
        limit: int | None = None,
    ) -> list[dict[str, Any]]:
        """Query records from table."""
        logger = get_logger(__name__)
        try:
            if table_name not in self._tables:
                logger.error(f"Table not found: {table_name}")
                return []

            table_class = self._tables[table_name]
            session = self.Session()

            try:
                query = session.query(table_class)

                # Apply filters
                if filters:
                    for key, value in filters.items():
                        if hasattr(table_class, key):
                            query = query.filter(getattr(table_class, key) == value)

                # Apply limit
                if limit:
                    query = query.limit(limit)

                # Execute query
                records = query.all()

                # Convert to dictionaries
                result = []
                for record in records:
                    record_dict: dict[str, Any] = {}
                    for column in table_class.__table__.columns:
                        record_dict[column.name] = getattr(record, column.name)
                    result.append(record_dict)

                logger.info(f"Queried {len(result)} records from {table_name}")
                return result

            finally:
                session.close()

        except (OSError, RuntimeError, ValueError):
            logger.exception(f"Failed to query records from {table_name}")
            return []

    def update_record(
        self,
        table_name: str,
        record_id: int,
        data: dict[str, Any],
    ) -> bool:
        """Update a record in table."""
        logger = get_logger(__name__)
        try:
            if table_name not in self._tables:
                logger.error(f"Table not found: {table_name}")
                return False

            table_class = self._tables[table_name]
            session = self.Session()

            try:
                record = session.query(table_class).filter(table_class.id == record_id).first()
                if not record:
                    logger.error(f"Record not found: {table_name}.{record_id}")
                    return False

                # Update fields
                for key, value in data.items():
                    if hasattr(record, key) and key != "id":
                        setattr(record, key, value)

                session.commit()
                logger.info(f"Updated record {table_name}.{record_id}")
                return True

            finally:
                session.close()

        except (OSError, RuntimeError, ValueError):
            logger.exception(f"Failed to update record {table_name}.{record_id}")
            return False

    def delete_record(self, table_name: str, record_id: int) -> bool:
        """Delete a record from table."""
        logger = get_logger(__name__)
        try:
            if table_name not in self._tables:
                logger.error(f"Table not found: {table_name}")
                return False

            table_class = self._tables[table_name]
            session = self.Session()

            try:
                record = session.query(table_class).filter(table_class.id == record_id).first()
                if not record:
                    logger.error(f"Record not found: {table_name}.{record_id}")
                    return False

                session.delete(record)
                session.commit()
                logger.info(f"Deleted record {table_name}.{record_id}")
                return True

            finally:
                session.close()

        except (OSError, RuntimeError, ValueError):
            logger.exception(f"Failed to delete record {table_name}.{record_id}")
            return False

    def get_table_info(self, table_name: str) -> dict[str, Any] | None:
        """Get information about a table."""
        logger = get_logger(__name__)
        try:
            if table_name not in self._tables:
                logger.error(f"Table not found: {table_name}")
                return None

            table_class = self._tables[table_name]

            columns = [
                {
                    "name": column.name,
                    "type": str(column.type),
                    "nullable": column.nullable,
                    "primary_key": column.primary_key,
                }
                for column in table_class.__table__.columns
            ]

            # Get record count
            session = self.Session()
            try:
                record_count = session.query(table_class).count()
            finally:
                session.close()
        except (OSError, RuntimeError, ValueError):
            logger.exception(f"Failed to get table info for {table_name}")
            return None
        else:
            return {
                "name": table_name,
                "columns": columns,
                "record_count": record_count,
            }

    def list_tables(self) -> list[str]:
        """List all available tables."""
        return list(self._tables.keys())

    def backup_database(self, backup_path: Path) -> bool:
        """Create database backup."""
        logger = get_logger(__name__)
        try:
            if self.database_url.startswith("sqlite:///"):
                db_path = Path(self.database_url.replace("sqlite:///", ""))
                if db_path.exists():
                    import shutil

                    shutil.copy2(db_path, backup_path)
                    logger.info(f"Database backed up to: {backup_path}")
                    return True
                logger.error("Source database file not found")
                return False
            logger.error("Backup only supported for SQLite databases")
        except (OSError, RuntimeError, ValueError):
            logger.exception("Failed to backup database")
            return False
        else:
            return False

    def close(self) -> None:
        """Close database connections."""
        self.engine.dispose()
        logger = get_logger(__name__)
        logger.info("Database connections closed")


# Predefined table schemas for LSCSIM
PRODUCT_TABLE_COLUMNS = [
    ColumnDefinition("product_name", "string", nullable=False, unique=True),
    ColumnDefinition("product_code", "string", unique=True),
    ColumnDefinition("description", "text"),
    ColumnDefinition("category", "string"),
    ColumnDefinition("status", "string", default_value="active"),
    ColumnDefinition("version", "string", default_value="1.0.0"),
]

SIMULATION_TABLE_COLUMNS = [
    ColumnDefinition("simulation_name", "string", nullable=False),
    ColumnDefinition("project_id", "integer", nullable=False),
    ColumnDefinition("analysis_type", "string"),
    ColumnDefinition("solver_type", "string"),
    ColumnDefinition("status", "string", default_value="pending"),
    ColumnDefinition("start_time", "datetime"),
    ColumnDefinition("end_time", "datetime"),
    ColumnDefinition("result_file_path", "string"),
]

MATERIAL_TABLE_COLUMNS = [
    ColumnDefinition("material_name", "string", nullable=False, unique=True),
    ColumnDefinition("density", "float"),
    ColumnDefinition("young_modulus", "float"),
    ColumnDefinition("poisson_ratio", "float"),
    ColumnDefinition("yield_strength", "float"),
    ColumnDefinition("description", "text"),
    ColumnDefinition("category", "string"),
]
