"""Time series utils."""

from __future__ import annotations

from collections.abc import Callable
from sqlite3 import Connection
from typing import TYPE_CHECKING, NamedTuple, cast
from uuid import UUID

from loguru import logger

if TYPE_CHECKING:
    from .context import TranslationContext


class TransferStats(NamedTuple):
    """Class that represents the transfer status of time series."""

    transferred: int
    updated: int
    children_remapped: int


UNIQUE_TS_COLUMNS: tuple[str, ...] = (
    "owner_uuid",
    "owner_type",
    "owner_category",
    "time_series_uuid",
    "name",
    "time_series_type",
    "features",
)


def _main_db_path(conn: Connection) -> str | None:
    """Return filesystem path for the main SQLite database, if present."""
    try:
        for _, name, path in conn.execute("PRAGMA database_list").fetchall():
            if name == "main" and path:
                return str(path)
    except Exception:
        return None
    return None


def _ts_columns(conn: Connection) -> list[str]:
    """Return ordered column names excluding the autoincrement primary key."""
    rows = conn.execute("PRAGMA table_info(time_series_associations)").fetchall()
    return [row[1] for row in rows if row[1] != "id"]


def _deduplicate_ts_associations(conn: Connection, unique_cols: tuple[str, ...]) -> int:
    """Remove duplicate association rows using the unique key columns."""
    group_by = ",".join(unique_cols)
    before = conn.total_changes
    conn.execute(
        f"""
        DELETE FROM time_series_associations
        WHERE rowid NOT IN (
            SELECT MIN(rowid)
            FROM time_series_associations
            GROUP BY {group_by}
        )
        """
    )
    return conn.total_changes - before


def _count_ts_associations(conn: Connection) -> int:
    """Return total rows in time series associations."""
    count = conn.execute("SELECT COUNT(*) FROM time_series_associations").fetchone()[0]
    return int(count)


def transfer_time_series_metadata(context: TranslationContext) -> TransferStats:
    """Transfer time series metadata for target system."""
    uuid_map = context.target_system._component_mgr._components_by_uuid

    logger.info(f"Transferring time series metadata for {len(uuid_map)} components")

    with (
        context.source_system.open_time_series_store(mode="r") as src_store,
        context.target_system.open_time_series_store(mode="a") as tgt_store,
    ):
        src_metadata = src_store.metadata_conn
        tgt_metadata = tgt_store.metadata_conn
        src_associations = context.source_system._component_mgr._associations._con

        uuid_to_type = {str(uuid): type(comp).__name__ for uuid, comp in uuid_map.items()}

        tgt_metadata.execute("DROP TABLE IF EXISTS target_components")
        tgt_metadata.execute("CREATE TEMP TABLE target_components (uuid TEXT PRIMARY KEY, type TEXT)")
        tgt_metadata.executemany("INSERT INTO target_components VALUES (?, ?)", list(uuid_to_type.items()))

        child_parent_rows = src_associations.execute("""
            SELECT component_uuid, attached_component_uuid
            FROM component_associations
        """).fetchall()

        child_remapping = [
            (child_uuid, parent_uuid, type(uuid_map[UUID(parent_uuid)]).__name__)
            for child_uuid, parent_uuid in child_parent_rows
            if parent_uuid in uuid_to_type
        ]

        tgt_metadata.execute("DROP TABLE IF EXISTS child_mapping")
        tgt_metadata.execute(
            "CREATE TEMP TABLE child_mapping (child_uuid TEXT, parent_uuid TEXT, parent_type TEXT)"
        )
        if child_remapping:
            tgt_metadata.executemany("INSERT INTO child_mapping VALUES (?, ?, ?)", child_remapping)

        removed_duplicates = _deduplicate_ts_associations(tgt_metadata, UNIQUE_TS_COLUMNS)
        if removed_duplicates:
            logger.warning("Removed {} duplicate time series association rows", removed_duplicates)

        initial_count = _count_ts_associations(tgt_metadata)

        columns = _ts_columns(tgt_metadata)
        column_csv = ",".join(columns)
        placeholders = ",".join(["?"] * len(columns))

        transferred = 0
        src_db_path = _main_db_path(src_metadata)

        if src_db_path:
            tgt_metadata.execute("ATTACH DATABASE ? AS src_ts", (src_db_path,))
            try:
                tgt_metadata.execute(
                    f"""
                    INSERT OR IGNORE INTO time_series_associations ({column_csv})
                    SELECT {column_csv}
                    FROM src_ts.time_series_associations s
                    WHERE NOT EXISTS (
                        SELECT 1 FROM time_series_associations t
                        WHERE t.owner_uuid = s.owner_uuid
                          AND t.owner_type = s.owner_type
                          AND t.owner_category = s.owner_category
                          AND t.time_series_uuid = s.time_series_uuid
                          AND t.name = s.name
                          AND t.time_series_type = s.time_series_type
                          AND t.features = s.features
                    )
                    """
                )
            finally:
                try:
                    tgt_metadata.execute("DETACH DATABASE src_ts")
                except Exception as exc:  # pragma: no cover - defensive
                    logger.warning("Could not detach src_ts during time series transfer: {}", exc)
        else:
            src_rows = src_metadata.execute(f"SELECT {column_csv} FROM time_series_associations").fetchall()
            if src_rows:
                tgt_metadata.executemany(
                    f"INSERT OR IGNORE INTO time_series_associations ({column_csv}) VALUES ({placeholders})",
                    src_rows,
                )

        result = tgt_metadata.execute("""
            WITH owner_resolution AS (
                SELECT
                    ts.owner_uuid as original_uuid,
                    COALESCE(tc_direct.uuid, cm.parent_uuid) as resolved_uuid,
                    COALESCE(tc_direct.type, cm.parent_type) as resolved_type
                FROM time_series_associations ts
                LEFT JOIN target_components tc_direct ON ts.owner_uuid = tc_direct.uuid
                LEFT JOIN child_mapping cm ON ts.owner_uuid = cm.child_uuid
                WHERE tc_direct.uuid IS NOT NULL OR cm.parent_uuid IS NOT NULL
            )
            UPDATE time_series_associations
            SET
                owner_uuid = (SELECT resolved_uuid FROM owner_resolution WHERE original_uuid = time_series_associations.owner_uuid),
                owner_type = (SELECT resolved_type FROM owner_resolution WHERE original_uuid = time_series_associations.owner_uuid)
            WHERE owner_uuid IN (SELECT original_uuid FROM owner_resolution)
        """)

        updated = max(result.rowcount if result.rowcount is not None else 0, 0)
        children_remapped = len(child_remapping) if child_remapping else 0

        removed_after_update = _deduplicate_ts_associations(tgt_metadata, UNIQUE_TS_COLUMNS)
        if removed_after_update:
            logger.warning(
                "Removed {} duplicate time series association rows after remapping", removed_after_update
            )

        tgt_metadata.execute(
            """
            CREATE UNIQUE INDEX IF NOT EXISTS idx_ts_owner_series_unique
            ON time_series_associations (
                owner_uuid,
                owner_type,
                owner_category,
                time_series_uuid,
                name,
                time_series_type,
                features
            )
            """
        )

        final_count = _count_ts_associations(tgt_metadata)
        transferred = max(0, final_count - initial_count)

    # We need to rebuild the time series to have the objects in memory.
    loader = cast(
        Callable[[], None],
        context.target_system._time_series_mgr._metadata_store._load_metadata_into_memory,
    )
    loader()

    logger.info(
        f"Time series metadata: {transferred} transferred, {updated} updated, {children_remapped} children remapped"
    )

    return TransferStats(transferred=transferred, updated=updated, children_remapped=children_remapped)
