"""Source platform introspection strategy.

Encapsulates platform-specific behavior needed during delta capture:

* **Identifier normalization** — each RDBMS stores unquoted identifiers
  in a different case (SQL Server preserves original, Redshift/PostgreSQL
  lowercases, Snowflake uppercases).

* **Column introspection** — queries the source catalog to identify
  platform-managed columns (temporal, identity, computed) so they can
  be excluded from delta comparison on the validate side.

Use :func:`get_source_introspector` to obtain the right implementation
for a given dialect string.
"""

from __future__ import annotations

import logging
from abc import ABC, abstractmethod
from typing import Any, TYPE_CHECKING

if TYPE_CHECKING:
    from .models import TableConfig

logger = logging.getLogger(__name__)

ColumnMetadata = dict[str, dict[str, list[str]]]
"""Per-table column metadata.

Keys are normalised ``SCHEMA.TABLE``.  Values contain:

* ``platform_managed_columns`` — temporal + computed columns
* ``identity_columns`` — auto-increment identity columns
"""


class SourceIntrospector(ABC):
    """Strategy interface for source-platform–specific introspection."""

    @abstractmethod
    def normalize_case(self, name: str) -> str:
        """Normalize an identifier to the platform's storage convention."""

    @abstractmethod
    def introspect_columns(
        self,
        connection: Any,
        table_configs: list[TableConfig],
    ) -> ColumnMetadata:
        """Query the source catalog for platform-managed columns.

        Args:
            connection: Raw database connection.
            table_configs: :class:`TableConfig` objects identifying the
                tables to inspect.

        Returns:
            Per-table metadata dict.  Empty when no special columns
            are found or the platform has none.
        """


# ---------------------------------------------------------------------------
# SQL Server
# ---------------------------------------------------------------------------


class SqlServerIntrospector(SourceIntrospector):
    """SQL Server: preserves identifier case, introspects ``sys.columns``."""

    _QUERY = """\
SELECT
    s.name   AS schema_name,
    t.name   AS table_name,
    c.name   AS column_name,
    c.generated_always_type,
    c.is_identity,
    c.is_computed
FROM sys.columns c
JOIN sys.tables  t ON c.object_id = t.object_id
JOIN sys.schemas s ON t.schema_id = s.schema_id
WHERE (c.generated_always_type <> 0
       OR c.is_identity = 1
       OR c.is_computed = 1)
  AND ({table_filter})
"""

    def normalize_case(self, name: str) -> str:
        return name

    def introspect_columns(
        self,
        connection: Any,
        table_configs: list[TableConfig],
    ) -> ColumnMetadata:
        if not table_configs:
            return {}

        table_filter = " OR ".join(
            f"(s.name = '{tc.schema_name}' AND t.name = '{tc.table_name}')"
            for tc in table_configs
        )
        sql = self._QUERY.format(table_filter=table_filter)

        metadata: ColumnMetadata = {}
        cursor = connection.cursor()
        try:
            cursor.execute(sql)
            for row in cursor.fetchall():
                schema_name = row[0].upper()
                table_name = row[1].upper()
                column_name = row[2].upper()
                generated_always_type = row[3]
                is_identity = row[4]
                is_computed = row[5]

                fqn = f"{schema_name}.{table_name}"
                entry = metadata.setdefault(fqn, {
                    "platform_managed_columns": [],
                    "identity_columns": [],
                })

                if generated_always_type != 0 or is_computed:
                    if column_name not in entry["platform_managed_columns"]:
                        entry["platform_managed_columns"].append(column_name)

                if is_identity:
                    if column_name not in entry["identity_columns"]:
                        entry["identity_columns"].append(column_name)

        except Exception as exc:
            logger.warning("SQL Server column introspection failed: %s", exc)
        finally:
            cursor.close()

        return metadata


# ---------------------------------------------------------------------------
# Redshift
# ---------------------------------------------------------------------------


class RedshiftIntrospector(SourceIntrospector):
    """Redshift/PostgreSQL: lowercases identifiers, introspects identity columns."""

    _QUERY = """\
SELECT
    c.table_schema  AS schema_name,
    c.table_name    AS table_name,
    c.column_name   AS column_name,
    c.column_default
FROM information_schema.columns c
WHERE c.column_default LIKE '%%identity%%'
  AND ({table_filter})
"""

    def normalize_case(self, name: str) -> str:
        return name.lower()

    def introspect_columns(
        self,
        connection: Any,
        table_configs: list[TableConfig],
    ) -> ColumnMetadata:
        if not table_configs:
            return {}

        table_filter = " OR ".join(
            f"(c.table_schema = '{tc.schema_name}' AND c.table_name = '{tc.table_name}')"
            for tc in table_configs
        )
        sql = self._QUERY.format(table_filter=table_filter)

        metadata: ColumnMetadata = {}
        cursor = connection.cursor()
        try:
            cursor.execute(sql)
            for row in cursor.fetchall():
                schema_name = row[0].upper()
                table_name = row[1].upper()
                column_name = row[2].upper()

                fqn = f"{schema_name}.{table_name}"
                entry = metadata.setdefault(fqn, {
                    "platform_managed_columns": [],
                    "identity_columns": [],
                })

                if column_name not in entry["identity_columns"]:
                    entry["identity_columns"].append(column_name)
                if column_name not in entry["platform_managed_columns"]:
                    entry["platform_managed_columns"].append(column_name)

        except Exception as exc:
            logger.warning("Redshift column introspection failed: %s", exc)
        finally:
            cursor.close()

        return metadata


# ---------------------------------------------------------------------------
# Teradata
# ---------------------------------------------------------------------------


class TeradataIntrospector(SourceIntrospector):
    """Teradata: uppercases identifiers, introspects ``DBC.ColumnsV`` for identity columns.

    Introspection is best-effort: if the connected user lacks SELECT
    access on ``DBC.ColumnsV`` the method returns empty metadata so that
    baseline capture can proceed without interruption.
    """

     #TODO: The user may need to be granted SELECT access on ``DBC.ColumnsV`` to perform introspection.
    _QUERY = """\
SELECT
    c.DatabaseName  AS schema_name,
    c.TableName     AS table_name,
    c.ColumnName    AS column_name,
    c.IdColType
FROM DBC.ColumnsV c
WHERE c.IdColType IN ('GA', 'GD')
  AND ({table_filter})
"""

    def normalize_case(self, name: str) -> str:
        return name.upper()

    def introspect_columns(
        self,
        connection: Any,
        table_configs: list[TableConfig],
    ) -> ColumnMetadata:
        if not table_configs:
            return {}

        table_filter = " OR ".join(
            f"(c.DatabaseName = '{tc.schema_name}' AND c.TableName = '{tc.table_name}')"
            for tc in table_configs
        )
        sql = self._QUERY.format(table_filter=table_filter)

        metadata: ColumnMetadata = {}
        try:
            cursor = connection.cursor()
        except Exception as exc:
            logger.warning("Teradata column introspection skipped (cursor error): %s", exc)
            return metadata

        try:
            cursor.execute(sql)
            for row in cursor.fetchall():
                schema_name = row[0].strip().upper()
                table_name = row[1].strip().upper()
                column_name = row[2].strip().upper()

                fqn = f"{schema_name}.{table_name}"
                entry = metadata.setdefault(fqn, {
                    "platform_managed_columns": [],
                    "identity_columns": [],
                })

                if column_name not in entry["identity_columns"]:
                    entry["identity_columns"].append(column_name)
                if column_name not in entry["platform_managed_columns"]:
                    entry["platform_managed_columns"].append(column_name)

        except Exception as exc:
            logger.warning("Teradata column introspection failed: %s", exc)
        finally:
            try:
                cursor.close()
            except Exception:
                pass

        return metadata


# ---------------------------------------------------------------------------
# Default (fallback for unsupported dialects)
# ---------------------------------------------------------------------------

class DefaultIntrospector(SourceIntrospector):
    """Fallback: uppercases identifiers, no column introspection."""

    def normalize_case(self, name: str) -> str:
        return name.upper()

    def introspect_columns(
        self,
        connection: Any,
        table_configs: list[TableConfig],
    ) -> ColumnMetadata:
        return {}
