"""Table registry for managing lazy table reflection and attribute access."""
import reprlib
from sqlalchemy import MetaData
from sqlalchemy.engine import reflection
from sqlalchemy.ext.automap import automap_base
from sqlalchemy.orm.attributes import InstrumentedAttribute

from .exceptions import TableNotFoundError, PrimaryKeyError


class TableRegistry:
    """Manages lazy table reflection and provides transparent table access."""

    def __init__(self, bind, schema=None):
        """
        Initialize the table registry.

        Args:
            bind: SQLAlchemy engine or connection string
            schema: Optional database schema name
        """
        self._bind = bind
        self._schema = schema
        self._metadata = MetaData(schema=schema)
        self._tables = set()
        self._reflected_tables = {}
        self._load_table_names()

    def _load_table_names(self):
        """Load all table names from the database."""
        insp = reflection.Inspector.from_engine(bind=self._bind)
        self._tables = set(insp.get_table_names(schema=self._schema))

    @property
    def available_tables(self):
        """Get a list of all available table names."""
        return list(self._tables)

    def table_exists(self, table_name):
        """Check if a table exists in the database."""
        return table_name in self._tables

    def get_table_variants(self, table_name):
        """Get all case variants of a table name that should map to the same table."""
        variants = [table_name]
        if table_name != table_name.lower():
            variants.append(table_name.lower())
        if table_name != table_name.upper():
            variants.append(table_name.upper())
        return variants

    def is_table_uninitialized(self, table_name):
        """Check if a table attribute is not yet initialized."""
        # This replaces the dangerous 'None' string check
        try:
            value = self._reflected_tables.get(table_name)
            return value is None
        except (AttributeError, KeyError):
            return True

    def _reflect_table(self, table_name):
        """
        Reflect a specific table and return the mapped class.

        Args:
            table_name: Name of the table to reflect

        Returns:
            Mapped SQLAlchemy table class

        Raises:
            PrimaryKeyError: If the table has no primary key
            TableNotFoundError: If the table doesn't exist
        """
        if not self.table_exists(table_name):
            raise TableNotFoundError(
                f"Table '{table_name}' not found in database. "
                f"Available tables: {reprlib.repr(self.available_tables)}"
            )

        try:
            # Clear any existing metadata for this table
            self._metadata.clear()

            # Reflect only the requested table
            self._metadata.reflect(bind=self._bind, only=[table_name])

            # Create automap base and prepare
            Base = automap_base(metadata=self._metadata)
            Base.prepare()

            # Get the mapped class
            table_class = Base.classes[table_name]

            # Add case-insensitive column access
            self._add_column_variants(table_class)

            return table_class

        except KeyError as e:
            raise PrimaryKeyError(
                f"Table '{table_name}' must have a primary key for ORM mapping. "
                f"Available tables: {reprlib.repr(self.available_tables)}"
            ) from e
        except Exception as e:
            raise TableNotFoundError(
                f"Failed to reflect table '{table_name}': {str(e)}. "
                f"Available tables: {reprlib.repr(self.available_tables)}"
            ) from e

    def _add_column_variants(self, table_class):
        """Add case-insensitive access to table columns."""
        column_names = [key for key in table_class.__dict__.keys()
                       if not key.startswith('_')]

        for column_name in column_names:
            column = getattr(table_class, column_name)
            if isinstance(column, InstrumentedAttribute):
                # Add uppercase and lowercase variants for column access
                if column_name != column_name.upper():
                    setattr(table_class, column_name.upper(), column)
                if column_name != column_name.lower():
                    setattr(table_class, column_name.lower(), column)

    def get_table(self, table_name):
        """
        Get a table, reflecting it lazily if necessary.

        Args:
            table_name: Name of the table to get

        Returns:
            Mapped SQLAlchemy table class

        Raises:
            TableNotFoundError: If the table doesn't exist
            PrimaryKeyError: If the table has no primary key
        """
        # Try direct lookup first
        if table_name in self._reflected_tables:
            return self._reflected_tables[table_name]

        # If not found, try case-insensitive variants
        for variant in self.get_table_variants(table_name):
            if variant in self._reflected_tables:
                self._reflected_tables[table_name] = self._reflected_tables[variant]
                return self._reflected_tables[variant]

        # If still not found, reflect the table
        try:
            table_class = self._reflect_table(table_name)
            self._reflected_tables[table_name] = table_class

            # Store variants for future access
            for variant in self.get_table_variants(table_name):
                if variant != table_name:
                    self._reflected_tables[variant] = table_class

            return table_class

        except (TableNotFoundError, PrimaryKeyError):
            # Try lowercase and uppercase variants
            for variant in self.get_table_variants(table_name):
                try:
                    table_class = self._reflect_table(variant)
                    self._reflected_tables[table_name] = table_class
                    self._reflected_tables[variant] = table_class
                    return table_class
                except (TableNotFoundError, PrimaryKeyError):
                    continue

            # Re-raise the original error if all variants failed
            raise

    def reflect_all_tables(self):
        """
        Reflect all tables in the database.

        Returns:
            Dictionary mapping table names to mapped classes
        """
        self._metadata.reflect(bind=self._bind)
        Base = automap_base(metadata=self._metadata)
        Base.prepare()

        reflected_classes = {}
        for table_name, table_class in Base.classes.items():
            self._add_column_variants(table_class)
            reflected_classes[table_name] = table_class

            # Store case variants
            for variant in self.get_table_variants(table_name):
                if variant != table_name:
                    reflected_classes[variant] = table_class

        self._reflected_tables.update(reflected_classes)
        return reflected_classes

    def refresh(self):
        """Refresh the table registry by reloading table names."""
        self._reflected_tables.clear()
        self._metadata.clear()
        self._load_table_names()