"""dedup_duckdb_table_by_hash — Remove duplicate rows from a DuckDB table using md5 hash of data columns."""

from __future__ import annotations

import os
import time
from typing import Any


def dedup_duckdb_table_by_hash(
    duckdb_path: str,
    table: str,
    exclude_cols: list[str] | None = None,
) -> dict[str, Any]:
    """Remove duplicate rows from a DuckDB table by computing md5 hash of data columns.

    Args:
        duckdb_path: Path to DuckDB file. Absolute or relative to FN_REGISTRY_ROOT.
        table: Table name to deduplicate.
        exclude_cols: Columns to exclude from hash computation (metadata cols).
                      Defaults to ["run_id", "extracted_at", "row_hash"].

    Returns:
        dict with keys: status, rows_before, rows_after, dedup_removed,
        duration_ms, hash_column.
    """
    import duckdb  # type: ignore

    t0 = time.monotonic()

    # Resolve path against FN_REGISTRY_ROOT if relative
    if not os.path.isabs(duckdb_path):
        root = os.environ.get("FN_REGISTRY_ROOT", os.getcwd())
        duckdb_path = os.path.join(root, duckdb_path)

    if exclude_cols is None:
        exclude_cols = ["run_id", "extracted_at", "row_hash"]

    exclude_set = {c.lower() for c in exclude_cols}

    conn = duckdb.connect(duckdb_path)
    try:
        # Verify table exists
        tables = [r[0] for r in conn.execute("SHOW TABLES").fetchall()]
        if table not in tables:
            return {
                "status": "error",
                "error": f"Table '{table}' not found in {duckdb_path}. Available: {tables}",
                "rows_before": 0,
                "rows_after": 0,
                "dedup_removed": 0,
                "duration_ms": int((time.monotonic() - t0) * 1000),
                "hash_column": "row_hash",
            }

        # Introspect columns
        desc = conn.execute(f'DESCRIBE "{table}"').fetchall()
        all_cols = [r[0] for r in desc]
        existing_col_names_lower = {c.lower() for c in all_cols}

        # Add row_hash column if missing (idempotent)
        if "row_hash" not in existing_col_names_lower:
            conn.execute(f'ALTER TABLE "{table}" ADD COLUMN row_hash VARCHAR')
            all_cols.append("row_hash")
            existing_col_names_lower.add("row_hash")

        # Data columns = all columns minus excluded
        data_cols = [c for c in all_cols if c.lower() not in exclude_set]

        if not data_cols:
            return {
                "status": "error",
                "error": "No data columns remaining after exclusion.",
                "rows_before": 0,
                "rows_after": 0,
                "dedup_removed": 0,
                "duration_ms": int((time.monotonic() - t0) * 1000),
                "hash_column": "row_hash",
            }

        # Build md5 expression: md5(col1 || '\t' || col2 || ...)
        # Each col: COALESCE(CAST("colname" AS VARCHAR), '')
        parts = " || '\t' || ".join(
            f"COALESCE(CAST(\"{c}\" AS VARCHAR), '')" for c in data_cols
        )
        hash_expr = f"md5({parts})"

        # Update row_hash where NULL
        conn.execute(
            f'UPDATE "{table}" SET row_hash = {hash_expr} WHERE row_hash IS NULL'
        )

        # Count rows before dedup
        rows_before = conn.execute(f'SELECT count(*) FROM "{table}"').fetchone()[0]

        # Delete duplicates, keeping row with smallest rowid (earliest insert)
        conn.execute(
            f"""
            DELETE FROM "{table}"
            WHERE rowid NOT IN (
                SELECT min(rowid) FROM "{table}" GROUP BY row_hash
            )
            """
        )

        # Count rows after dedup
        rows_after = conn.execute(f'SELECT count(*) FROM "{table}"').fetchone()[0]

    finally:
        conn.close()

    duration_ms = int((time.monotonic() - t0) * 1000)
    dedup_removed = rows_before - rows_after

    return {
        "status": "ok",
        "rows_before": rows_before,
        "rows_after": rows_after,
        "dedup_removed": dedup_removed,
        "duration_ms": duration_ms,
        "hash_column": "row_hash",
    }


if __name__ == "__main__":
    import argparse
    import json

    parser = argparse.ArgumentParser(description="Dedup a DuckDB table by row hash.")
    parser.add_argument("duckdb_path", help="Path to DuckDB file")
    parser.add_argument("table", help="Table name to deduplicate")
    parser.add_argument(
        "--exclude-cols",
        nargs="*",
        default=None,
        help="Columns to exclude from hash (default: run_id extracted_at row_hash)",
    )
    args = parser.parse_args()

    result = dedup_duckdb_table_by_hash(args.duckdb_path, args.table, args.exclude_cols)
    print(json.dumps(result, indent=2))
