from __future__ import annotations

from typing import Any, Dict, List, Optional, Set

import sqlalchemy as sa
from sqlalchemy import delete, select
from sqlalchemy.orm import Session

from db.channel_exports import resolve_push_skus
from db.channel_sku_mapping import mapping_by_master
from db.models import MagentoSyncState


def align_magento_sync_state_keys(
    session: Session,
    connection_id: int,
    *,
    dry_run: bool = True,
    only_assigned: bool = True,
    skus: Optional[List[str]] = None,
) -> Dict[str, Any]:
    """Re-key magento_sync_state rows from master_sku → channel_sku when prefix/mapping differs.

    Master-catalog Magento push uses channel_sku as the sync planner SKU. Legacy Plytix sync
    state may still be stored under the master (or Plytix) SKU string.
    """
    wanted: Set[str] = resolve_push_skus(
        session,
        "magento",
        skus=skus,
        only_assigned=only_assigned,
    )
    if not wanted:
        return {
            "connection_id": connection_id,
            "dry_run": dry_run,
            "candidate_count": 0,
            "migrated_count": 0,
            "skipped_both_exist": 0,
            "migrations": [],
        }

    sku_map = mapping_by_master(session, "magento", sorted(wanted), connection_id=connection_id)
    migrations: List[Dict[str, Any]] = []
    skipped_both = 0
    migrated = 0

    for master_sku in sorted(wanted):
        channel_sku = str(sku_map.get(master_sku) or master_sku).strip()
        if not channel_sku or channel_sku == master_sku:
            continue
        old_state = session.scalar(
            select(MagentoSyncState)
            .where(MagentoSyncState.connection_id == connection_id)
            .where(MagentoSyncState.sku == master_sku)
        )
        if old_state is None:
            continue
        new_state = session.scalar(
            select(MagentoSyncState)
            .where(MagentoSyncState.connection_id == connection_id)
            .where(MagentoSyncState.sku == channel_sku)
        )
        if new_state is not None:
            skipped_both += 1
            migrations.append(
                {
                    "master_sku": master_sku,
                    "channel_sku": channel_sku,
                    "action": "skipped_target_exists",
                }
            )
            continue
        migrations.append(
            {
                "master_sku": master_sku,
                "channel_sku": channel_sku,
                "action": "migrate",
                "data_hash": old_state.data_hash,
                "images_hash": old_state.images_hash,
                "relations_hash": old_state.relations_hash,
                "associations_hash": getattr(old_state, "associations_hash", None),
            }
        )
        if dry_run:
            continue
        session.add(
            MagentoSyncState(
                connection_id=connection_id,
                sku=channel_sku,
                data_hash=old_state.data_hash,
                images_hash=old_state.images_hash,
                relations_hash=old_state.relations_hash,
                associations_hash=getattr(old_state, "associations_hash", None),
                last_seen_in_feed_at=old_state.last_seen_in_feed_at,
                last_pushed_at=old_state.last_pushed_at,
                last_error=old_state.last_error,
            )
        )
        session.execute(
            delete(MagentoSyncState).where(
                MagentoSyncState.connection_id == connection_id,
                MagentoSyncState.sku == master_sku,
            )
        )
        migrated += 1

    return {
        "connection_id": connection_id,
        "dry_run": dry_run,
        "candidate_count": len([m for m in migrations if m["action"] == "migrate"]),
        "migrated_count": migrated,
        "skipped_both_exist": skipped_both,
        "migrations": migrations[:500],
    }
