from __future__ import annotations
import uuid
import json
import logging
from datetime import datetime
from typing import Dict, Any, List

from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import text

# NOTE: we will reuse the same engine client used in L2A in a follow-up.
# from .engine_client import EngineClient

log = logging.getLogger(__name__)


async def ensure_dedupe_suggestions_schema(db: AsyncSession):
    """Canonical schema owner for the CRM dedupe suggestions store."""
    try:
        result = await db.execute(text("PRAGMA table_info(dedupe_suggestions)"))
        columns = result.fetchall()

        if columns:
            id_col = next((c for c in columns if c[1] == "id"), None)
            if id_col and "INTEGER" in str(id_col[2]).upper():
                await db.execute(text("DROP TABLE IF EXISTS dedupe_suggestions"))
                columns = []
            else:
                existing = {c[1] for c in columns}
                if "cluster_id" not in existing:
                    await db.execute(
                        text(
                            "ALTER TABLE dedupe_suggestions ADD COLUMN cluster_id TEXT"
                        )
                    )
                    columns = list(columns) + [(None, "cluster_id", "TEXT", 0, None, 0)]

        if not columns:
            await db.execute(
                text(
                    """
        CREATE TABLE dedupe_suggestions (
            id TEXT PRIMARY KEY,
            account_id TEXT NOT NULL,
            object_type TEXT NOT NULL,
            rec_id_1 TEXT NOT NULL,
            rec_id_2 TEXT NOT NULL,
            score REAL NOT NULL,
            tier TEXT,
            reasons TEXT,
            field_score_details TEXT,
            explanation TEXT,
            status TEXT NOT NULL DEFAULT 'pending',
            created_at TEXT DEFAULT CURRENT_TIMESTAMP,
            decided_at TEXT,
            decided_by TEXT,
            cluster_id TEXT
        )
        """
                )
            )

        index_sql = [
            """
        CREATE UNIQUE INDEX IF NOT EXISTS ix_dedupe_unique_pair
            ON dedupe_suggestions(account_id, object_type, rec_id_1, rec_id_2)
        """,
            """
        CREATE INDEX IF NOT EXISTS ix_dedupe_status_score
            ON dedupe_suggestions(account_id, object_type, status, score DESC)
        """,
            """
        CREATE INDEX IF NOT EXISTS ix_dedupe_cluster
            ON dedupe_suggestions(account_id, cluster_id, status)
        """,
            """
        CREATE INDEX IF NOT EXISTS idx_dedupe_acc_obj
            ON dedupe_suggestions(account_id, object_type)
        """,
            """
        CREATE INDEX IF NOT EXISTS idx_dedupe_status
            ON dedupe_suggestions(status)
        """,
            """
        CREATE INDEX IF NOT EXISTS idx_dedupe_score
            ON dedupe_suggestions(score DESC)
        """,
            """
        CREATE INDEX IF NOT EXISTS idx_dedupe_suggestions_created
            ON dedupe_suggestions(account_id, object_type, status, created_at)
        """,
        ]
        for stmt in index_sql:
            await db.execute(text(stmt))

        await db.commit()
    except Exception:
        await db.rollback()
        raise


class IntelligentDedupeService:
    """
    Persistence/store helper for CRM dedupe suggestions.
    """

    def __init__(self, db: AsyncSession, account_id: str):
        self.db = db
        self.account_id = str(account_id)

    async def _ensure_table(self):
        await ensure_dedupe_suggestions_schema(self.db)

    async def save_suggestions(
        self,
        object_type: str,
        pairs: List[Dict[str, Any]],
        status: str = "pending",
        run_id: str | None = None,
        created_at: datetime | None = None,
    ):
        """
        Persist a batch of candidate pairs.
        `pairs` entries should have: left_id, right_id, score (0..1), tier, reasons, field_score_details, explanation?, sig?
        We normalize the pair order to (min, max) to avoid symmetric duplicates.

        Args:
            created_at: Optional timestamp to use for all records (for consistent flip window comparison)
        """
        await self._ensure_table()
        if not pairs:
            return 0
        # Use provided timestamp or create new one
        timestamp = created_at or datetime.utcnow()
        now = timestamp.isoformat(sep=" ", timespec="seconds")
        sql = text("""
        INSERT INTO dedupe_suggestions
        (id, account_id, object_type, rec_id_1, rec_id_2, score, tier, reasons, field_score_details, explanation, status, created_at)
        VALUES (:id, :account_id, :object_type, :rec1, :rec2, :score, :tier, :reasons, :details, :explanation, :status, :created_at)
        ON CONFLICT (account_id, object_type, rec_id_1, rec_id_2) DO NOTHING
        """)
        for p in pairs:
            # Support both old (left_id/right_id) and new (rec_id_1/rec_id_2) formats
            a = p.get("rec_id_1") or p.get("left_id")
            b = p.get("rec_id_2") or p.get("right_id")
            rec1, rec2 = (a, b) if a <= b else (b, a)

            # Add signal type to reasons if available
            reasons = p.get("reasons", "")
            if p.get("sig"):
                try:
                    reasons_dict = (
                        json.loads(reasons) if reasons and reasons != "{}" else {}
                    )
                    reasons_dict["sig"] = p["sig"]
                    reasons = json.dumps(reasons_dict)
                except:
                    pass

            await self.db.execute(
                sql,
                {
                    "id": str(uuid.uuid4()),
                    "account_id": self.account_id,
                    "object_type": object_type,
                    "rec1": rec1,
                    "rec2": rec2,
                    "score": float(p.get("score", 0.0)),
                    "tier": p.get("tier", "POSSIBLE"),
                    "reasons": reasons,
                    "details": p.get("field_score_details", ""),
                    "explanation": p.get("explanation", ""),
                    "status": status,
                    "created_at": timestamp,
                },
            )
        await self.db.commit()
        return len(pairs)
