"""Delta capture orchestrator and strategy selector.

Currently only ``FULL_SNAPSHOT`` is implemented.  Future strategies:

.. todo:: See SNOW-3289286 and SNOW-3289292

   * **PK_DIFF** — compare before/after row hashes keyed by primary key;
     detects inserts, deletes, and updates without copying full row data.
   * **SYNTHETIC_KEY** — hash all columns into a deterministic surrogate key,
     then apply PK_DIFF logic; handles tables with no natural primary key.
"""

from __future__ import annotations

import logging
from typing import Any

from ..common.config import ReplayConfiguration
from .models import DeltaStrategy, RowDict, TableConfig
from .strategies import DeltaCaptureStrategy
from .strategies.full_snapshot import FullSnapshotStrategy

logger = logging.getLogger(__name__)

_FULL_SNAPSHOT = FullSnapshotStrategy()


def select_strategy(
    table_cfg: TableConfig,
    replay_cfg: ReplayConfiguration,
) -> DeltaCaptureStrategy:
    """Pick a delta strategy for *table_cfg* given *replay_cfg* thresholds.

    Currently always returns ``FULL_SNAPSHOT``.

    .. todo:: Wire up PK_DIFF / SYNTHETIC_KEY once PK discovery and
              large-table support are implemented.
    """
    return _FULL_SNAPSHOT


def snapshot_table(
    connection: Any,
    schema: str,
    table: str,
    database: str | None = None,
) -> list[RowDict]:
    """Capture all rows from ``schema.table`` via ``SELECT *``.

    When *database* is provided the query is fully qualified
    (``"database"."schema"."table"``), which is required when the target
    lives in a clone database that differs from the session's current DB.

    Binary columns (``bytes``) are hex-encoded so that downstream
    consumers (Pandas, DuckDB, Polars) can process them safely.
    """
    cursor = connection.cursor()
    try:
        if database:
            cursor.execute(f'SELECT * FROM "{database}"."{schema}"."{table}"')
        else:
            cursor.execute(f'SELECT * FROM "{schema}"."{table}"')
        columns = [desc[0].upper() for desc in cursor.description]
        rows: list[RowDict] = []
        for raw_row in cursor.fetchall():
            rows.append({
                col: (val.hex() if isinstance(val, (bytes, bytearray)) else val)
                for col, val in zip(columns, raw_row)
            })
        return rows
    finally:
        cursor.close()


def compute_table_delta(
    before: list[RowDict],
    after: list[RowDict],
    table_cfg: TableConfig,
    replay_cfg: ReplayConfiguration,
) -> dict[str, Any]:
    """Compute a delta between *before* and *after* using the selected strategy."""
    strategy = select_strategy(table_cfg, replay_cfg)
    table_cfg.strategy = strategy.name

    logger.info(
        "Computing delta for %s using %s (rows_before=%d, rows_after=%d)",
        table_cfg.fqn, strategy.name.value, len(before), len(after),
    )

    return strategy.compute_delta(before, after, table_cfg)
