from __future__ import annotations

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

from sqlalchemy import select
from sqlalchemy.orm import Session

from db.compat_connections import SHOPIFY_COMPAT_ID_OFFSET, decode_compat_connection_id
from db.models import (
    MagentoProductSourceSnapshot,
    PlytixProductSourceSnapshot,
    ShopifyProductSourceSnapshot,
)
from db.source_snapshot_enrichment import (
    ENRICHMENT_PROFILES,
    apply_snapshot_patches,
    extract_image_urls,
)


def run_channel_enrichment_pull(
    session: Session,
    *,
    channel_type: str,
    connection_id: Optional[int] = None,
    native_connection_id: Optional[int] = None,
    profiles: Optional[Sequence[str]] = None,
    skus: Optional[Sequence[str]] = None,
    dry_run: bool = False,
    limit: Optional[int] = None,
) -> Dict[str, Any]:
    """One-time isolated pull for explicit field groups (images, Magento option attrs, etc.)."""
    wanted = _normalize_profiles(profiles)
    if not wanted:
        return {"status": "failed", "error": "At least one enrichment profile is required"}

    channel_type = (channel_type or "").strip().lower()
    if channel_type == "magento":
        patches = _fetch_magento_patches(session, native_connection_id or connection_id, wanted, skus, limit)
        channel = "magento"
        conn_id = native_connection_id or connection_id
    elif channel_type == "shopify":
        patches = _fetch_shopify_patches(session, connection_id, native_connection_id, wanted, skus, limit)
        channel = "shopify"
        conn_id = native_connection_id
    elif channel_type in {"plytix", "generic"}:
        patches = _fetch_plytix_patches(session, wanted, skus, limit)
        channel = "plytix"
        conn_id = None
    else:
        return {"status": "failed", "error": f"Unsupported channel_type: {channel_type}"}

    result = apply_snapshot_patches(
        session,
        channel,
        patches,
        connection_id=conn_id,
        source_label=f"enrichment:{','.join(sorted(wanted))}",
        dry_run=dry_run,
    )
    result["profiles"] = sorted(wanted)
    result["candidate_skus"] = len(patches)
    return result


def _normalize_profiles(profiles: Optional[Sequence[str]]) -> Set[str]:
    wanted: Set[str] = set()
    for item in profiles or []:
        code = str(item or "").strip().lower()
        if code in ENRICHMENT_PROFILES:
            wanted.add(code)
    return wanted


def _fetch_magento_patches(
    session: Session,
    connection_id: Optional[int],
    profiles: Set[str],
    skus: Optional[Sequence[str]],
    limit: Optional[int],
) -> Dict[str, Dict[str, Any]]:
    from db.magento_repositories import SqlAlchemyMagentoConnectionRepository
    from magento.magento_api import MagentoRestClient
    from magento.oauth_client import MagentoOAuthClient, build_magento_oauth_kwargs

    if not connection_id:
        raise ValueError("Magento connection_id is required")
    conn = SqlAlchemyMagentoConnectionRepository(session).get_for_sync(connection_id)
    if not conn:
        raise ValueError(f"Magento connection {connection_id} not found")

    oauth = MagentoOAuthClient(**build_magento_oauth_kwargs(conn))
    api = MagentoRestClient(oauth)
    target_skus = _resolve_target_skus(session, "magento", skus, connection_id=connection_id, limit=limit)
    patches: Dict[str, Dict[str, Any]] = {}
    page = 1
    found: Set[str] = set()
    while True:
        status, items, total, err = api.list_products(page_size=100, current_page=page)
        if status != 200 or err:
            raise RuntimeError(err or f"Magento HTTP {status}")
        if not items:
            break
        for product in items:
            if not isinstance(product, dict):
                continue
            sku = str(product.get("sku") or "").strip()
            if not sku or (target_skus and sku not in target_skus):
                continue
            patch: Dict[str, Any] = {}
            if "images" in profiles:
                media_entries = product.get("media_gallery_entries") or []
                patch["media_gallery_entries"] = media_entries
                urls = extract_image_urls({"media_gallery_entries": media_entries}, channel="magento")
                if urls:
                    patch["primary_image_url"] = urls[0]
                    patch["image_urls"] = urls
            if "magento_custom_attributes" in profiles and product.get("custom_attributes"):
                patch["custom_attributes"] = product.get("custom_attributes")
            if patch:
                patches[sku] = patch
                found.add(sku)
            if target_skus and target_skus.issubset(found):
                break
        if target_skus and target_skus.issubset(found):
            break
        if total is not None and page * 100 >= total:
            break
        page += 1
    return patches


def _fetch_shopify_patches(
    session: Session,
    connection_id: Optional[int],
    native_connection_id: Optional[int],
    profiles: Set[str],
    skus: Optional[Sequence[str]],
    limit: Optional[int],
) -> Dict[str, Dict[str, Any]]:
    from db.models import ShopifyConnection
    from shopify.connections import build_client
    from shopify.pull_query import build_shopify_media_pull_query, extract_shopify_media

    native_id = native_connection_id
    if connection_id and connection_id >= SHOPIFY_COMPAT_ID_OFFSET:
        channel_type, native_id = decode_compat_connection_id(connection_id)
        if channel_type != "shopify":
            raise ValueError(f"Connection {connection_id} is not Shopify")
    connection = session.get(ShopifyConnection, native_id) if native_id else None
    if connection is None:
        raise ValueError("Shopify connection not found")
    client = build_client(connection)
    query = build_shopify_media_pull_query(client)
    target_skus = _resolve_target_skus(session, "shopify", skus, connection_id=native_id, limit=limit)
    patches: Dict[str, Dict[str, Any]] = {}
    after: Optional[str] = None
    while True:
        data = client.graphql(query, {"first": 50, "after": after})
        connection_data = data.get("products") or {}
        for product in connection_data.get("nodes") or []:
            if not isinstance(product, dict):
                continue
            media_patch = extract_shopify_media(product) if "shopify_media" in profiles or "images" in profiles else {}
            variants = ((product.get("variants") or {}).get("nodes") or [])
            for variant in variants:
                if not isinstance(variant, dict):
                    continue
                sku = str(variant.get("sku") or "").strip()
                if not sku or (target_skus and sku not in target_skus):
                    continue
                patch = dict(media_patch)
                patch["shopify_product_id"] = product.get("id")
                patch["shopify_variant_id"] = variant.get("id")
                if patch:
                    patches[sku] = patch
        page_info = connection_data.get("pageInfo") or {}
        if not page_info.get("hasNextPage"):
            break
        after = page_info.get("endCursor")
        if not after:
            break
        if target_skus and target_skus.issubset(patches.keys()):
            break
    return patches


def _fetch_plytix_patches(
    session: Session,
    profiles: Set[str],
    skus: Optional[Sequence[str]],
    limit: Optional[int],
) -> Dict[str, Dict[str, Any]]:
    if "plytix_assets" not in profiles and "images" not in profiles:
        return {}
    target_skus = _resolve_target_skus(session, "plytix", skus, limit=limit)
    stmt = select(PlytixProductSourceSnapshot.sku, PlytixProductSourceSnapshot.payload).where(
        PlytixProductSourceSnapshot.valid_to.is_(None)
    )
    if target_skus:
        stmt = stmt.where(PlytixProductSourceSnapshot.sku.in_(sorted(target_skus)))
    patches: Dict[str, Dict[str, Any]] = {}
    asset_keys = (
        "thumbnail",
        "Thumbnail",
        "assets",
        "images",
        "image",
        "media",
        "gallery",
        "base_image",
        "additional_images",
    )
    for sku, payload in session.execute(stmt).all():
        if not sku or not isinstance(payload, dict):
            continue
        patch: Dict[str, Any] = {}
        for key in asset_keys:
            if key in payload and payload[key] not in (None, "", [], {}):
                patch[key] = payload[key]
        urls = extract_image_urls({**payload, **patch}, channel="plytix")
        if urls:
            patch["thumbnail_url"] = urls[0]
            patch["image_urls"] = urls
        if patch:
            patches[str(sku)] = patch
    return patches


def _resolve_target_skus(
    session: Session,
    channel: str,
    skus: Optional[Sequence[str]],
    *,
    connection_id: Optional[int] = None,
    limit: Optional[int] = None,
) -> Optional[Set[str]]:
    explicit = {str(s).strip() for s in (skus or []) if str(s).strip()}
    if explicit:
        return explicit
    if limit is None:
        return None
    if channel == "magento":
        model = MagentoProductSourceSnapshot
    elif channel == "shopify":
        model = ShopifyProductSourceSnapshot
    else:
        model = PlytixProductSourceSnapshot
    stmt = select(model.sku).where(model.valid_to.is_(None)).order_by(model.sku).limit(int(limit))
    if connection_id is not None and channel in {"magento", "shopify"}:
        stmt = stmt.where(model.connection_id == connection_id)
    return {str(sku) for (sku,) in session.execute(stmt).all() if sku}
