"""Build Magento-normalizable snapshot rows from the master catalog channel pipeline."""

from __future__ import annotations

from typing import Any, Dict, List, Optional

from sqlalchemy.orm import Session

from db.channel_exports import build_channel_product_payloads, resolve_push_skus
from db.channel_image_push import build_image_push_items
from db.channel_capabilities import resolve_product_structure_for_connection
from channel.url_canonical import build_pdp_slug
from db.source_imports import normalize_column_key
from normalize import _is_empty


def build_magento_snapshot_rows_from_master(
    session: Session,
    *,
    skus: Optional[List[str]] = None,
    only_assigned: bool = True,
    connection_id: Optional[int] = None,
    capabilities: Optional[Dict[str, Any]] = None,
    expand_relations: Optional[bool] = None,
) -> List[Dict[str, Any]]:
    """Produce row dicts for magento.catalog_normalize / sync_planner."""
    structure = resolve_product_structure_for_connection(
        session,
        "magento",
        connection_id=connection_id,
        capabilities=capabilities,
    )
    wanted = resolve_push_skus(
        session,
        "magento",
        skus=skus,
        only_assigned=only_assigned,
        product_structure=structure,
        capabilities=capabilities,
        expand_relations=expand_relations,
    )
    if not wanted:
        return []

    payloads = build_channel_product_payloads(
        session,
        "magento",
        skus=sorted(wanted),
        only_assigned=False,
        connection_id=connection_id,
        product_structure=structure,
        capabilities=capabilities,
    )
    rows: List[Dict[str, Any]] = []
    for payload in payloads:
        row = _payload_to_snapshot_row(session, payload, connection_id=connection_id)
        relation_fields = payload.get("relation_fields") or {}
        row.update(relation_fields)
        association_fields = payload.get("association_fields") or {}
        if association_fields:
            row["association_fields"] = association_fields
            row["related_skus"] = ",".join(association_fields.get("related") or [])
            row["upsell_skus"] = ",".join(association_fields.get("upsell") or [])
            row["crosssell_skus"] = ",".join(association_fields.get("crosssell") or [])
        rows.append(row)
    _enrich_configurable_parent_rows(rows)
    return rows


def build_magento_image_rows_from_master(
    session: Session,
    *,
    skus: Optional[List[str]] = None,
    only_assigned: bool = True,
    connection_id: Optional[int] = None,
    limit: Optional[int] = None,
) -> List[Dict[str, Any]]:
    """Minimal snapshot rows for images-only Magento sync (sku + gallery URLs only)."""
    items = build_image_push_items(
        session,
        "magento",
        skus=skus,
        limit=limit,
        only_assigned=only_assigned,
        connection_id=connection_id,
    )
    rows: List[Dict[str, Any]] = []
    for item in items:
        urls = item.get("image_urls") or []
        row: Dict[str, Any] = {
            "sku": item["channel_sku"],
            "master_sku": item["master_sku"],
            "name": item.get("name") or item["channel_sku"],
            "base_image": urls[0],
        }
        if len(urls) > 1:
            row["additional_images"] = ",".join(urls[1:])
            row["image_urls"] = ",".join(urls)
        rows.append(row)
    return rows


def _payload_to_snapshot_row(
    session: Optional[Session] | Dict[str, Any],
    payload: Optional[Dict[str, Any]] = None,
    *,
    connection_id: Optional[int] = None,
) -> Dict[str, Any]:
    if payload is None:
        payload = session  # Backward-compatible direct test/helper call.
        session = None
    canonical = dict(payload.get("canonical_fields") or {})
    master_sku = str(payload.get("sku") or canonical.get("sku") or "").strip()
    channel_sku = str(payload.get("channel_sku") or master_sku).strip()
    row: Dict[str, Any] = {"sku": channel_sku, "master_sku": master_sku}

    name = canonical.get("title") or canonical.get("name") or canonical.get("display_name")
    if name:
        row["name"] = name

    price = payload.get("price")
    if price is not None:
        row["price"] = price
    else:
        # Normalize quarantines rows without price; sync planner applies real price at upsert.
        row["price"] = 1

    category_ids, mapped_category_paths = _magento_taxonomy_targets(
        session if isinstance(session, Session) else None,
        master_sku,
        canonical,
        connection_id=connection_id,
    )
    # In master-catalog mode, taxonomy targets are canonical.  Do not fall back
    # to raw slug paths such as "kitchen-cabinets/base-cabinets"; Magento treats
    # those as literal category names and may try to create duplicates under
    # root when a numeric target was not found.
    if isinstance(session, Session):
        categories = ",".join(mapped_category_paths)
    else:
        categories = ",".join(mapped_category_paths) if mapped_category_paths else _category_path(canonical)
    if category_ids:
        row["category_ids"] = category_ids
    if categories:
        row["categories"] = categories
        row["categories_raw"] = categories

    # Prefer aliased channel fields (1→N fan-out). Fall back to canonical when
    # callers (tests/helpers) only supply canonical_fields.
    channel_fields = dict(payload.get("fields") or {})
    attr_source = channel_fields if channel_fields else canonical

    # Never let attribute/override "name" clobber master title — that left Magento
    # on stale SEO/Plytix titles while dashboard showed the clean master name.
    protected_name = row.get("name")
    if not protected_name and not _is_empty(attr_source.get("name")):
        protected_name = attr_source.get("name")
    for key, value in attr_source.items():
        if _is_empty(value):
            continue
        norm = normalize_column_key(key)
        if norm in {"sku", "title", "name"}:
            continue
        row[norm] = value
    if protected_name:
        row["name"] = protected_name

    role = payload.get("product_role") or "standalone"
    if role == "parent":
        row.setdefault("product_type", "configurable")
    elif role == "child":
        row.setdefault("product_type", "simple")
    else:
        row.setdefault("product_type", str(canonical.get("product_type") or "simple"))

    return row


def _enrich_configurable_parent_rows(rows: List[Dict[str, Any]]) -> None:
    """Copy child gallery to image-less parents; set canonical PDP slug from clean title + SKU."""
    by_sku = {str(row.get("sku") or "").strip(): row for row in rows if str(row.get("sku") or "").strip()}
    by_master = {
        str(row.get("master_sku") or row.get("sku") or "").strip(): row
        for row in rows
        if str(row.get("master_sku") or row.get("sku") or "").strip()
    }

    for row in rows:
        product_type = str(row.get("product_type") or "").strip().lower()
        if "configurable" not in product_type:
            continue

        if _is_empty(row.get("base_image")):
            for child_sku in _child_skus_from_row(row):
                child = by_sku.get(child_sku) or by_master.get(child_sku)
                if not child or _is_empty(child.get("base_image")):
                    continue
                row["base_image"] = child.get("base_image")
                for field in ("small_image", "thumbnail_image", "thumbnail", "image_urls"):
                    if _is_empty(row.get(field)) and not _is_empty(child.get(field)):
                        row[field] = child.get(field)
                if _is_empty(row.get("additional_images")) and not _is_empty(child.get("additional_images")):
                    row["additional_images"] = child.get("additional_images")
                break

        from magento.payload_mapper import resolve_product_display_name

        title = resolve_product_display_name(row)
        slug_base = title.split("(")[0].strip() or title
        sku = str(row.get("sku") or "").strip()
        if slug_base and sku:
            row["product_url_slug"] = build_pdp_slug(slug_base, sku)


def _child_skus_from_row(row: Dict[str, Any]) -> List[str]:
    from magento.sync_hashes import _parse_children_from_row

    children, _, _ = _parse_children_from_row(row)
    if children:
        return children
    variant_list = str(row.get("variant_list") or "").strip()
    if not variant_list:
        return []
    return [part.strip() for part in variant_list.split(",") if part.strip()]


def _magento_taxonomy_targets(
    session: Optional[Session],
    master_sku: str,
    canonical: Dict[str, Any],
    *,
    connection_id: Optional[int],
) -> tuple[List[int], List[str]]:
    if session is None:
        return [], []
    from db.product_channel_taxonomy import resolve_magento_category_targets

    return resolve_magento_category_targets(
        session,
        master_sku=master_sku,
        canonical_fields=canonical,
        connection_id=connection_id,
    )


def _category_ids_from_rule(rule: Dict[str, Any]) -> List[int]:
    raw = rule.get("category_ids", rule.get("category_id"))
    if raw is None:
        return []
    values = raw if isinstance(raw, list) else str(raw).replace("|", ",").split(",")
    ids: List[int] = []
    for value in values:
        text = str(value or "").strip()
        if text.isdigit():
            ids.append(int(text))
    return ids


def _category_paths_from_rule(rule: Dict[str, Any]) -> List[str]:
    raw = rule.get("category_paths", rule.get("category_path", rule.get("path")))
    if raw is None:
        return []
    values = raw if isinstance(raw, list) else str(raw).split("|")
    return [str(value or "").strip() for value in values if str(value or "").strip()]


def _category_path(canonical: Dict[str, Any]) -> str:
    parts = [
        str(canonical.get("category_l1") or "").strip(),
        str(canonical.get("category_l2") or "").strip(),
        str(canonical.get("category_l3") or "").strip(),
    ]
    parts = [p for p in parts if p]
    return "/".join(parts)
