"""Magento native product-link sync (related / upsell / crosssell)."""

from __future__ import annotations

import logging
from typing import Any, Dict, List, Optional, Protocol, Tuple

from db.association_rules import LINK_TYPES, magento_link_type
from db.master_associations import compute_associations_hash
from magento.sync_hashes import _canonical_json

logger = logging.getLogger(__name__)


class MagentoProductLinksClient(Protocol):
    def get_product_links(
        self, sku: str, link_type: str
    ) -> Tuple[int, List[Dict[str, Any]], Optional[str]]: ...

    def assign_product_links(
        self, sku: str, items: List[Dict[str, Any]]
    ) -> Tuple[int, Optional[Any], Optional[str]]: ...

    def remove_product_link(
        self, sku: str, link_type: str, linked_product_sku: str
    ) -> Tuple[int, Optional[str]]: ...


def associations_from_row(row: Dict[str, Any]) -> Dict[str, List[str]]:
    fields = row.get("association_fields")
    if isinstance(fields, dict):
        return {
            link_type: [
                str(sku).strip()
                for sku in (fields.get(link_type) or [])
                if str(sku).strip()
            ]
            for link_type in LINK_TYPES
        }
    out: Dict[str, List[str]] = {}
    mapping = {
        "related": "related_skus",
        "upsell": "upsell_skus",
        "crosssell": "crosssell_skus",
    }
    for link_type, column in mapping.items():
        raw = str(row.get(column) or "").strip()
        if not raw:
            out[link_type] = []
            continue
        for sep in ("|", ";"):
            raw = raw.replace(sep, ",")
        out[link_type] = [part.strip() for part in raw.split(",") if part.strip()]
    return out


def compute_associations_hash_from_row(row: Dict[str, Any]) -> str:
    return compute_associations_hash(associations_from_row(row))


def execute_set_associations(
    client: MagentoProductLinksClient,
    sku: str,
    row: Dict[str, Any],
) -> Dict[str, Any]:
    """Sync Magento product links to match association_fields on the row."""
    desired = associations_from_row(row)
    added = 0
    removed = 0
    errors: List[str] = []

    for link_type in LINK_TYPES:
        magento_type = magento_link_type(link_type)
        want = desired.get(link_type) or []
        status, current_items, err = client.get_product_links(sku, magento_type)
        if status == 404:
            current_skus: List[str] = []
        elif status != 200:
            errors.append(f"{magento_type}:get:{err or status}")
            continue
        else:
            current_skus = [
                str(item.get("linked_product_sku") or "").strip()
                for item in current_items
                if isinstance(item, dict)
            ]
            current_skus = [s for s in current_skus if s]

        want_set = set(want)
        current_set = set(current_skus)

        for linked in sorted(current_set - want_set):
            del_status, del_err = client.remove_product_link(sku, magento_type, linked)
            if del_status not in (200, 204):
                errors.append(f"{magento_type}:delete:{linked}:{del_err or del_status}")
            else:
                removed += 1

        to_add = [target for target in want if target not in current_set]
        if to_add:
            items = [
                {
                    "sku": sku,
                    "link_type": magento_type,
                    "linked_product_sku": target,
                    "position": want.index(target) + 1,
                }
                for target in to_add
            ]
            post_status, _, post_err = client.assign_product_links(sku, items)
            if post_status not in (200, 201):
                errors.append(f"{magento_type}:assign:{post_err or post_status}")
            else:
                added += len(to_add)

    if errors:
        return {
            "status": "error",
            "sku": sku,
            "added": added,
            "removed": removed,
            "error": "; ".join(errors[:5]),
            "associations_hash": compute_associations_hash(desired),
            "debug": {"desired": desired, "canonical": _canonical_json(desired)},
        }
    return {
        "status": "success",
        "sku": sku,
        "added": added,
        "removed": removed,
        "associations_hash": compute_associations_hash(desired),
    }
