from __future__ import annotations

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

from sqlalchemy.orm import Session

from db.channel_aliases import channel_aliases_by_canonical
from db.channel_attribute_options import channel_attribute_options_bundle
from db.channel_exports import CHANNEL_CODES, resolve_pipeline_channel_code
from db.master_static_field_mapping import build_static_field_mapping_table
from db.source_imports import normalize_column_key


# Synthetic row for SKU (not always in master_attribute_definition).
SKU_MATRIX_DEFAULTS: Dict[str, str] = {
    "magento": "sku",
    "shopify": "sku",
    "plytix": "sku",
}


def build_master_channel_mapping_matrix(
    session: Session,
    *,
    magento_connection_id: Optional[int] = None,
    shopify_connection_id: Optional[int] = None,
    include_unmapped: bool = True,
) -> Dict[str, Any]:
    """Wide matrix: master attribute → channel attribute + registry match flag per channel."""
    table = build_static_field_mapping_table(
        session,
        include_inactive=False,
        magento_connection_id=magento_connection_id,
        shopify_connection_id=shopify_connection_id,
        include_channel_options=True,
    )
    channel_options = table.get("channel_options") or {}
    registry_codes = {
        channel: {str(opt.get("code") or "").strip() for opt in options if opt.get("code")}
        for channel, options in channel_options.items()
    }
    aliases_by_channel = {
        channel: channel_aliases_by_canonical(session, channel) for channel in CHANNEL_CODES
    }

    rows: List[Dict[str, Any]] = []
    seen_masters: Set[str] = set()

    for item in table.get("rows") or []:
        master_code = normalize_column_key(item.get("master_attribute_code"))
        if not master_code:
            continue
        seen_masters.add(master_code)
        rows.append(
            _matrix_row(
                master_code,
                item,
                aliases_by_channel,
                registry_codes,
            )
        )

    if "sku" not in seen_masters:
        rows.insert(
            0,
            _matrix_row(
                "sku",
                {"master_attribute_code": "sku", "master_label": "SKU", **SKU_MATRIX_DEFAULTS},
                aliases_by_channel,
                registry_codes,
                defaults=SKU_MATRIX_DEFAULTS,
            ),
        )

    if not include_unmapped:
        rows = [row for row in rows if any(row.get(ch) for ch in CHANNEL_CODES)]

    columns = _matrix_columns(list(CHANNEL_CODES))
    return {
        "matrix_kind": "master_channel_attribute",
        "channels": list(CHANNEL_CODES),
        "columns": columns,
        "row_count": len(rows),
        "mapped_count": sum(1 for row in rows if row.get("mapped_channel_count", 0) > 0),
        "validated_count": sum(
            1
            for row in rows
            if any(row.get(f"{ch}_is_match") == 1 for ch in CHANNEL_CODES)
        ),
        "rows": rows,
    }


def export_master_channel_mapping_matrix_csv_rows(
    session: Session,
    *,
    magento_connection_id: Optional[int] = None,
    shopify_connection_id: Optional[int] = None,
    include_unmapped: bool = True,
) -> List[Dict[str, Any]]:
    matrix = build_master_channel_mapping_matrix(
        session,
        magento_connection_id=magento_connection_id,
        shopify_connection_id=shopify_connection_id,
        include_unmapped=include_unmapped,
    )
    channels = matrix["channels"]
    csv_rows: List[Dict[str, Any]] = []
    for row in matrix["rows"]:
        item: Dict[str, Any] = {"Master": row["master"]}
        for channel in channels:
            item[channel] = row.get(channel) or ""
            item[f"{channel}_is_match"] = row.get(f"{channel}_is_match", 0)
        csv_rows.append(item)
    return csv_rows


def _matrix_row(
    master_code: str,
    static_item: Dict[str, Any],
    aliases_by_channel: Dict[str, Dict[str, Any]],
    registry_codes: Dict[str, Set[str]],
    *,
    defaults: Optional[Dict[str, str]] = None,
) -> Dict[str, Any]:
    row: Dict[str, Any] = {
        "master": master_code,
        "master_label": static_item.get("master_label") or master_code,
        "field_group": static_item.get("field_group") or "",
    }
    mapped_count = 0
    for channel in CHANNEL_CODES:
        channel = resolve_pipeline_channel_code(channel)
        mapped = _clean(static_item.get(channel))
        if not mapped:
            mapped = _alias_for_master(aliases_by_channel.get(channel, {}), master_code)
        if not mapped and defaults:
            mapped = defaults.get(channel) or ""
        row[channel] = mapped or ""
        is_match = 1 if mapped and mapped in registry_codes.get(channel, set()) else 0
        row[f"{channel}_is_match"] = is_match
        if mapped:
            mapped_count += 1
    row["mapped_channel_count"] = mapped_count
    return row


def _alias_for_master(aliases: Dict[str, Any], master_code: str) -> str:
    rows = aliases.get(master_code) or []
    if not rows:
        return ""
    static_rows = [alias for alias in rows if getattr(alias, "mapping_scope", "dynamic") == "static"]
    chosen = static_rows or rows
    return str(chosen[0].channel_attribute_code or "").strip()


def _matrix_columns(channels: List[str]) -> List[str]:
    columns = ["Master"]
    for channel in channels:
        columns.extend([channel, f"{channel}_is_match"])
    return columns


def _clean(value: Any) -> Optional[str]:
    if value is None:
        return None
    text = str(value).strip()
    if not text or text.lower() in {"nan", "none", "null"}:
        return None
    return text
