"""Plan and apply channel SKU renames from prefix rules (no master product push)."""

from __future__ import annotations

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

from sqlalchemy import select
from sqlalchemy.orm import Session

from db.channel_assignments import active_skus_for_channel
from db.channel_exports import resolve_pipeline_channel_code
from db.channel_sku_mapping import (
    ACTIVE_STATUS,
    reconcile_connection_id_for_channel,
    upsert_single_mapping,
)
from db.channel_sku_prefix_mapping import resolve_channel_sku_from_prefixes
from db.compat_connections import compat_connection_id, decode_compat_connection_id
from db.models import ChannelSkuMapping, MagentoCatalogState, MagentoConnection, MasterProduct, ShopifyConnection
from magento.magento_api import MagentoOAuthClient, MagentoRestClient
from magento.oauth_client import build_magento_oauth_kwargs
from magento.sku_rename import rename_product_sku
from shopify.connections import build_client, get_active_connection
from shopify.sku_rename import fetch_remote_variant_map, rename_variant_sku


DEFAULT_CHANNELS = ("shopify", "magento")
RENAME_CHANNELS = frozenset({"shopify", "magento"})


def evaluate_rename_candidate(
    *,
    master_sku: str,
    current_sku: str,
    target_sku: str,
    remote_skus: Optional[Set[str]] = None,
    has_mapping: bool = False,
) -> Dict[str, Any]:
    """Pure rename decision used by plan_channel_sku_renames (testable without DB)."""
    if target_sku == current_sku:
        return {"action": "skip", "reason": "already_correct", "master_sku": master_sku, "channel_sku": target_sku}

    if remote_skus is not None:
        if current_sku not in remote_skus:
            return {
                "action": "skip",
                "reason": "current_sku_not_on_remote",
                "master_sku": master_sku,
                "current_sku": current_sku,
                "target_sku": target_sku,
            }
        if target_sku in remote_skus and target_sku != current_sku:
            return {
                "action": "remap_existing",
                "reason": "target_sku_exists",
                "master_sku": master_sku,
                "current_sku": current_sku,
                "target_sku": target_sku,
            }

    if not has_mapping and remote_skus is not None and master_sku not in remote_skus:
        return {
            "action": "skip",
            "reason": "not_linked",
            "master_sku": master_sku,
            "current_sku": current_sku,
            "target_sku": target_sku,
        }

    return {
        "action": "rename",
        "master_sku": master_sku,
        "current_sku": current_sku,
        "target_sku": target_sku,
    }


def _master_skus(
    session: Session,
    *,
    channel_code: str,
    only_assigned: bool,
    skus: Optional[Iterable[str]] = None,
) -> List[str]:
    if skus:
        return sorted({str(s).strip() for s in skus if str(s).strip()})
    stmt = select(MasterProduct.sku).where(MasterProduct.is_active.is_(True))
    if only_assigned:
        assigned = active_skus_for_channel(session, channel_code)
        if not assigned:
            return []
        stmt = stmt.where(MasterProduct.sku.in_(sorted(assigned)))
    return [str(sku).strip() for (sku,) in session.execute(stmt.order_by(MasterProduct.sku)).all() if str(sku).strip()]


def _active_mappings(
    session: Session,
    channel_code: str,
    master_skus: List[str],
    *,
    connection_id: Optional[int],
) -> Dict[str, ChannelSkuMapping]:
    if not master_skus:
        return {}
    stmt = (
        select(ChannelSkuMapping)
        .where(ChannelSkuMapping.channel_code == channel_code)
        .where(ChannelSkuMapping.mapping_status == ACTIVE_STATUS)
        .where(ChannelSkuMapping.master_sku.in_(master_skus))
    )
    if connection_id is not None:
        stmt = stmt.where(ChannelSkuMapping.connection_id == connection_id)
    else:
        stmt = stmt.where(ChannelSkuMapping.connection_id.is_(None))
    return {row.master_sku: row for row in session.scalars(stmt).all()}


def plan_channel_sku_renames(
    session: Session,
    channel_code: str,
    *,
    connection_id: Optional[int] = None,
    only_assigned: bool = False,
    linked_only: bool = True,
    skus: Optional[Iterable[str]] = None,
    remote_skus: Optional[Set[str]] = None,
    remote_ids_by_sku: Optional[Dict[str, Optional[str]]] = None,
) -> Dict[str, Any]:
    """Build rename plan: current linked remote SKU → prefix-rule target SKU.

    Typical post-link flow: Shopify still has master SKUs (HPW-100) while prefix
    targets (HD-CW-100) do not exist yet — plan renames master → prefix on remote.
    """
    channel = resolve_pipeline_channel_code(channel_code)
    master_skus = _master_skus(session, channel_code=channel, only_assigned=only_assigned, skus=skus)
    mappings = _active_mappings(session, channel, master_skus, connection_id=connection_id)

    if linked_only and not skus:
        master_skus = sorted(set(master_skus) & set(mappings.keys()))

    planned: List[Dict[str, Any]] = []
    skipped: List[Dict[str, Any]] = []

    for master_sku in master_skus:
        target_sku = resolve_channel_sku_from_prefixes(
            session,
            master_sku,
            channel,
            connection_id=connection_id,
        )
        mapping = mappings.get(master_sku)
        current_sku = str(mapping.channel_sku).strip() if mapping else master_sku
        remote_id = str(mapping.remote_id).strip() if mapping and mapping.remote_id else None

        decision = evaluate_rename_candidate(
            master_sku=master_sku,
            current_sku=current_sku,
            target_sku=target_sku,
            remote_skus=remote_skus,
            has_mapping=mapping is not None,
        )
        if decision["action"] == "skip":
            skipped.append({k: v for k, v in decision.items() if k != "action"})
            continue

        action = decision["action"]
        action_remote_id = remote_id
        if action == "remap_existing" and remote_ids_by_sku is not None:
            target_remote_id = remote_ids_by_sku.get(target_sku)
            action_remote_id = str(target_remote_id).strip() if target_remote_id else None
        planned.append(
            {
                "action": action,
                "master_sku": master_sku,
                "current_sku": current_sku,
                "target_sku": target_sku,
                "remote_id": action_remote_id,
            }
        )

    return {
        "channel_code": channel,
        "connection_id": connection_id,
        "linked_only": linked_only,
        "master_count": len(master_skus),
        "planned_count": len(planned),
        "rename_count": sum(1 for row in planned if row.get("action") == "rename"),
        "remap_existing_count": sum(1 for row in planned if row.get("action") == "remap_existing"),
        "skipped_count": len(skipped),
        "planned": planned,
        "skipped": skipped[:500],
    }


def _shopify_client(session: Session, *, connection_id: Optional[int], native_connection_id: Optional[int]):
    connection = None
    if native_connection_id is not None:
        connection = session.get(ShopifyConnection, native_connection_id)
    elif connection_id:
        channel_type, native_id = decode_compat_connection_id(connection_id)
        if channel_type == "shopify":
            connection = session.get(ShopifyConnection, native_id)
    if connection is None:
        connection = get_active_connection(session)
    if connection is None:
        raise ValueError("No Shopify connection available")
    if connection.status != "active":
        raise ValueError("Shopify connection is disabled (kill switch engaged)")
    return build_client(connection), connection


def _magento_api(session: Session, connection_id: int) -> MagentoRestClient:
    conn = session.get(MagentoConnection, connection_id)
    if conn is None:
        raise ValueError(f"Magento connection {connection_id} not found")
    oauth = MagentoOAuthClient(**build_magento_oauth_kwargs(conn))
    return MagentoRestClient(oauth)


def _update_magento_catalog_sku(
    session: Session,
    *,
    connection_id: int,
    old_sku: str,
    new_sku: str,
    product_id: Optional[str],
) -> None:
    row = session.scalar(
        select(MagentoCatalogState)
        .where(MagentoCatalogState.connection_id == connection_id)
        .where(MagentoCatalogState.sku == old_sku)
    )
    if row is None:
        return
    existing_target = session.scalar(
        select(MagentoCatalogState.id)
        .where(MagentoCatalogState.connection_id == connection_id)
        .where(MagentoCatalogState.sku == new_sku)
    )
    if existing_target:
        session.delete(row)
        return
    row.sku = new_sku
    if product_id and not row.magento_product_id:
        try:
            row.magento_product_id = int(product_id)
        except (TypeError, ValueError):
            pass


def apply_channel_sku_renames(
    session: Session,
    *,
    channels: Optional[Iterable[str]] = None,
    only_assigned: bool = False,
    linked_only: bool = True,
    dry_run: bool = True,
    skus: Optional[Iterable[str]] = None,
    limit: Optional[int] = None,
    magento_connection_id: Optional[int] = None,
    shopify_connection_id: Optional[int] = None,
) -> Dict[str, Any]:
    """Rename remote SKUs to prefix-rule targets and update channel_sku_mapping rows."""
    selected = [resolve_pipeline_channel_code(ch) for ch in (channels or DEFAULT_CHANNELS)]
    summary: Dict[str, Any] = {
        "status": "ok",
        "dry_run": dry_run,
        "linked_only": linked_only,
        "only_assigned": only_assigned,
        "channels": {},
    }

    magento_native = magento_connection_id
    if magento_native is None:
        magento_native = session.scalars(select(MagentoConnection.id).order_by(MagentoConnection.id)).first()

    shopify = session.scalars(
        select(ShopifyConnection).where(ShopifyConnection.status == "active").order_by(ShopifyConnection.id)
    ).first()
    if shopify is None:
        shopify = session.scalars(select(ShopifyConnection).order_by(ShopifyConnection.id)).first()
    shopify_compat = shopify_connection_id or (compat_connection_id("shopify", shopify.id) if shopify else None)
    shopify_native = shopify.id if shopify else None

    for channel in selected:
        if channel not in RENAME_CHANNELS:
            summary["channels"][channel] = {
                "status": "skipped",
                "reason": "remote_rename_not_supported",
                "note": "Plytix has no remote SKU rename API; run link_master_skus first.",
            }
            continue

        connection_id = reconcile_connection_id_for_channel(
            channel,
            compat_connection_id=shopify_compat if channel == "shopify" else None,
            native_connection_id=magento_native if channel == "magento" else shopify_native,
        )

        remote_skus: Optional[Set[str]] = None
        remote_ids_by_sku: Dict[str, Optional[str]] = {}
        shopify_variant_map: Dict[str, Dict[str, Optional[str]]] = {}
        if channel == "shopify":
            client, _conn = _shopify_client(
                session,
                connection_id=shopify_compat,
                native_connection_id=shopify_native,
            )
            shopify_variant_map = fetch_remote_variant_map(client)
            remote_skus = set(shopify_variant_map.keys())
            remote_ids_by_sku = {
                sku: info.get("product_id")
                for sku, info in shopify_variant_map.items()
            }
        elif channel == "magento" and magento_native:
            remote_ids_by_sku = {
                str(sku).strip(): (str(product_id) if product_id is not None else None)
                for sku, product_id in session.execute(
                    select(MagentoCatalogState.sku, MagentoCatalogState.magento_product_id).where(
                        MagentoCatalogState.connection_id == magento_native
                    )
                ).all()
                if str(sku).strip()
            }
            remote_skus = set(remote_ids_by_sku)

        plan = plan_channel_sku_renames(
            session,
            channel,
            connection_id=connection_id,
            only_assigned=only_assigned,
            linked_only=linked_only,
            skus=skus,
            remote_skus=remote_skus,
            remote_ids_by_sku=remote_ids_by_sku,
        )
        items = list(plan["planned"])
        if limit:
            items = items[:limit]

        channel_result: Dict[str, Any] = {
            "connection_id": connection_id,
            "planned_count": plan["planned_count"],
            "rename_count": plan["rename_count"],
            "remap_existing_count": plan["remap_existing_count"],
            "skipped_count": plan["skipped_count"],
            "applied": [],
            "failed": [],
        }

        if dry_run:
            channel_result["preview"] = items[:500]
            channel_result["skipped"] = plan["skipped"][:100]
            channel_result["remaining_planned_count"] = plan["planned_count"]
            summary["channels"][channel] = channel_result
            continue

        shopify_client = None
        if channel == "shopify":
            shopify_client, _conn = _shopify_client(
                session,
                connection_id=shopify_compat,
                native_connection_id=shopify_native,
            )

        magento_api = _magento_api(session, magento_native) if channel == "magento" and magento_native else None

        for item in items:
            master_sku = item["master_sku"]
            current_sku = item["current_sku"]
            target_sku = item["target_sku"]
            action = item.get("action") or "rename"
            try:
                with session.begin_nested():
                    remote_id = item.get("remote_id")
                    if action == "remap_existing":
                        pass
                    elif channel == "shopify" and shopify_client:
                        variant_info = shopify_variant_map.get(current_sku)
                        if not variant_info or not variant_info.get("variant_id"):
                            raise RuntimeError(f"variant not found for SKU {current_sku}")
                        product_id = variant_info.get("product_id") or item.get("remote_id")
                        if not product_id:
                            raise RuntimeError(f"product id not found for SKU {current_sku}")
                        if target_sku in shopify_variant_map and target_sku != current_sku:
                            raise RuntimeError(f"target SKU already exists on Shopify: {target_sku}")
                        result = rename_variant_sku(
                            shopify_client,
                            variant_id=str(variant_info["variant_id"]),
                            product_id=str(product_id),
                            new_sku=target_sku,
                        )
                        remote_id = result.get("product_id") or product_id
                        shopify_variant_map.pop(current_sku, None)
                        shopify_variant_map[target_sku] = {
                            "variant_id": result.get("variant_id"),
                            "product_id": remote_id,
                        }
                        remote_ids_by_sku.pop(current_sku, None)
                        remote_ids_by_sku[target_sku] = remote_id
                    elif channel == "magento" and magento_api:
                        if target_sku in (remote_skus or set()) and target_sku != current_sku:
                            raise RuntimeError(f"target SKU already exists in Magento catalog: {target_sku}")
                        rename_product_sku(magento_api, current_sku, target_sku)
                        if remote_skus is not None:
                            remote_skus.discard(current_sku)
                            remote_skus.add(target_sku)
                        remote_ids_by_sku.pop(current_sku, None)
                        remote_ids_by_sku[target_sku] = item.get("remote_id")
                        _update_magento_catalog_sku(
                            session,
                            connection_id=magento_native,
                            old_sku=current_sku,
                            new_sku=target_sku,
                            product_id=item.get("remote_id"),
                        )
                        remote_id = item.get("remote_id")
                    else:
                        raise RuntimeError("channel client not configured")

                    match_source = "exact_remote_sku" if action == "remap_existing" else "prefix_rename"
                    notes = (
                        f"Linked to existing exact remote SKU {target_sku}; previous mapping was {current_sku}"
                        if action == "remap_existing"
                        else f"Renamed remote SKU {current_sku} -> {target_sku}"
                    )
                    upsert_single_mapping(
                        session,
                        master_sku=master_sku,
                        channel_sku=target_sku,
                        channel_code=channel,
                        connection_id=connection_id,
                        remote_id=str(remote_id).strip() if remote_id else None,
                        match_source=match_source,
                        notes=f"Renamed remote SKU {current_sku} → {target_sku}",
                    )
                channel_result["applied"].append(
                    {**item, "status": "remapped_existing" if action == "remap_existing" else "renamed"}
                )
            except Exception as exc:
                channel_result["failed"].append({**item, "error": str(exc)})

        follow_up = plan_channel_sku_renames(
            session,
            channel,
            connection_id=connection_id,
            only_assigned=only_assigned,
            linked_only=linked_only,
            skus=skus,
            remote_skus=remote_skus,
            remote_ids_by_sku=remote_ids_by_sku,
        )
        channel_result["remaining_planned_count"] = follow_up["planned_count"]
        channel_result["remaining_rename_count"] = follow_up["rename_count"]
        channel_result["remaining_remap_existing_count"] = follow_up["remap_existing_count"]
        channel_result["applied_count"] = len(channel_result["applied"])
        channel_result["failed_count"] = len(channel_result["failed"])
        summary["channels"][channel] = channel_result

    if any(ch.get("failed") for ch in summary["channels"].values() if isinstance(ch, dict)):
        summary["status"] = "partial"
    return summary
