"""Commit resolved import sessions into V2 products without inventing master data."""

from __future__ import annotations

from datetime import datetime, timezone
from typing import Any

from sqlalchemy import select
from sqlalchemy.orm import Session

from db.models_v2 import (
    V2Attribute,
    V2ImportMapping,
    V2Product,
    V2ProductCategory,
    V2ProductCollection,
    V2StagingBatch,
    V2StagingRow,
)
from db.v2_import.constants import RESOLVED_STATUSES, SELECTABLE_ATTRIBUTE_TYPES
from db.v2_import.matching import attribute_code_from_header, display_lookup_value, normalize_lookup_value


class CommitError(RuntimeError):
    pass


def _utcnow() -> datetime:
    return datetime.now(timezone.utc)


def commit_import(
    session: Session,
    batch: V2StagingBatch,
    *,
    catalog,
    committed_by: str,
) -> dict[str, Any]:
    mappings = list(
        session.execute(select(V2ImportMapping).where(V2ImportMapping.batch_id == batch.id)).scalars().all()
    )
    unresolved = [item for item in mappings if item.status not in RESOLVED_STATUSES]
    if unresolved:
        raise CommitError(f"{len(unresolved)} values must be reviewed before products can be imported.")

    index = {
        (item.entity_type, item.normalized_value, item.parent_key or ""): item
        for item in mappings
    }
    rows = list(
        session.execute(
            select(V2StagingRow).where(V2StagingRow.batch_id == batch.id).order_by(V2StagingRow.row_number.asc())
        ).scalars().all()
    )
    created = 0
    updated = 0
    skipped = 0
    failed: list[dict[str, Any]] = []
    created_master = {
        "brand": sum(1 for item in mappings if item.entity_type == "brand" and item.status == "created"),
        "hub": sum(1 for item in mappings if item.entity_type == "hub" and item.status == "created"),
        "category": sum(1 for item in mappings if item.entity_type == "category" and item.status == "created"),
        "collection": sum(1 for item in mappings if item.entity_type == "collection" and item.status == "created"),
        "attribute": sum(1 for item in mappings if item.entity_type == "attribute" and item.status == "created"),
        "attribute_option": sum(
            1 for item in mappings if item.entity_type == "attribute_option" and item.status == "created"
        ),
    }

    try:
        for row in rows:
            data = dict(row.normalized_data or {})
            if data.get("error") or row.status == "skipped":
                skipped += 1
                continue
            try:
                was_new = _upsert_product(session, data, index, catalog=catalog, source=f"csv:{batch.filename}")
            except Exception as exc:
                failed.append({"row_number": row.row_number, "sku": data.get("sku"), "error": str(exc)})
                raise CommitError(
                    f"Import stopped at row {row.row_number} (SKU {data.get('sku') or 'unknown'}): {exc}"
                ) from exc
            row.status = "promoted"
            row.updated_at = _utcnow()
            if was_new:
                created += 1
            else:
                updated += 1
        session.flush()
    except CommitError:
        for row in rows:
            if row.status == "promoted":
                row.status = "resolved"
        raise

    applied_mappings = sum(1 for item in mappings if item.status in {"mapped", "known_mapping", "exact_match"})
    return {
        "created_products": created,
        "updated_products": updated,
        "skipped_products": skipped,
        "failed_products": failed,
        "created_master_data": created_master,
        "applied_mappings": applied_mappings,
        "committed_by": committed_by,
    }


def _upsert_product(session: Session, data: dict[str, Any], index: dict, *, catalog, source: str) -> bool:
    sku = str(data.get("sku") or "").strip()
    if not sku:
        raise CommitError("Product SKU is missing.")
    lookups = _resolved_ids(data, index)
    attributes = _product_attributes(session, data, index, lookups)
    existing = session.execute(select(V2Product).where(V2Product.sku == sku)).scalar_one_or_none()
    now = _utcnow()
    was_new = existing is None
    if existing is None:
        product = V2Product(
            sku=sku,
            product_type=str(data.get("product_type") or "simple"),
            lifecycle_status=str(data.get("lifecycle_status") or "draft"),
            title=str(data.get("title") or sku),
            description=data.get("description") or None,
            source=source,
            attributes=attributes,
            created_at=now,
            updated_at=now,
        )
        session.add(product)
        session.flush()
    else:
        product = existing
        product.title = str(data.get("title") or product.title)
        if data.get("description"):
            product.description = data.get("description")
        if data.get("product_type"):
            product.product_type = str(data.get("product_type"))
        if data.get("lifecycle_status"):
            product.lifecycle_status = str(data.get("lifecycle_status"))
        merged = dict(product.attributes or {})
        merged.update(attributes)
        product.attributes = merged
        product.updated_at = now
        session.flush()

    hub_id = lookups["hub"]["target_id"]
    category_id = lookups["category"]["target_id"]
    collection_id = lookups["collection"]["target_id"]
    product.shopping_l1_category_id = hub_id
    product.shopping_l2_category_id = category_id
    product.primary_category_id = category_id or hub_id
    _replace_join_rows(session, product, hub_id, category_id, collection_id)
    return was_new


def _replace_join_rows(
    session: Session,
    product: V2Product,
    hub_id: int | None,
    category_id: int | None,
    collection_id: int | None,
) -> None:
    existing_cats = session.execute(
        select(V2ProductCategory).where(V2ProductCategory.product_id == product.id)
    ).scalars().all()
    keep_cat = {item for item in (hub_id, category_id) if item is not None}
    for row in existing_cats:
        if int(row.category_id) not in keep_cat:
            session.delete(row)
    have_cat = {int(row.category_id) for row in existing_cats}
    for category in keep_cat:
        if category not in have_cat:
            session.add(V2ProductCategory(product_id=int(product.id), category_id=int(category)))

    existing_cols = session.execute(
        select(V2ProductCollection).where(V2ProductCollection.product_id == product.id)
    ).scalars().all()
    keep_col = {collection_id} if collection_id is not None else set()
    for row in existing_cols:
        if int(row.collection_id) not in keep_col:
            session.delete(row)
    have_col = {int(row.collection_id) for row in existing_cols}
    if collection_id is not None and collection_id not in have_col:
        session.add(
            V2ProductCollection(
                product_id=int(product.id),
                collection_id=int(collection_id),
                is_primary=True,
            )
        )
    session.flush()


def _resolved_ids(data: dict[str, Any], index: dict) -> dict[str, dict[str, Any]]:
    out: dict[str, dict[str, Any]] = {}
    for role in ("brand", "hub", "category", "collection"):
        value = display_lookup_value(data.get(role) or "")
        item = index.get((role, normalize_lookup_value(value), "")) if value else None
        if value and (item is None or item.status not in RESOLVED_STATUSES):
            raise CommitError(f"{role} value {value!r} is not resolved.")
        if item is not None and item.status == "ignored":
            out[role] = {"target_id": None, "target_name": None}
            continue
        out[role] = {
            "target_id": int(item.matched_target_id) if item and item.matched_target_id is not None else None,
            "target_name": item.matched_target_name if item else None,
        }
    return out


def _product_attributes(session: Session, data: dict[str, Any], index: dict, lookups: dict) -> dict[str, Any]:
    payload: dict[str, Any] = {}
    if lookups["brand"]["target_id"] is not None:
        payload["brand_id"] = lookups["brand"]["target_id"]
        payload["brand"] = lookups["brand"]["target_name"]
    if lookups["hub"]["target_name"]:
        payload["category_l1"] = lookups["hub"]["target_name"]
    if lookups["category"]["target_name"]:
        payload["category"] = lookups["category"]["target_name"]
    if lookups["collection"]["target_name"]:
        payload["collection"] = lookups["collection"]["target_name"]

    for header, value in (data.get("attributes") or {}).items():
        attr_item = index.get(("attribute", normalize_lookup_value(header), ""))
        if attr_item is None or attr_item.status == "ignored":
            continue
        if attr_item.status not in RESOLVED_STATUSES or attr_item.matched_target_id is None:
            raise CommitError(f"Attribute {header!r} is not resolved.")
        attribute = session.get(V2Attribute, int(attr_item.matched_target_id))
        if attribute is None:
            raise CommitError(f"Attribute {header!r} no longer exists.")
        code = attribute.code or attribute_code_from_header(header)
        option_item = index.get(("attribute_option", normalize_lookup_value(value), normalize_lookup_value(header)))
        if attribute.data_type in SELECTABLE_ATTRIBUTE_TYPES:
            if option_item is None or option_item.status not in RESOLVED_STATUSES:
                raise CommitError(f"Attribute value {value!r} for {header} is not resolved.")
            if option_item.status == "ignored":
                continue
            payload[code] = option_item.matched_target_name or value
            if option_item.matched_target_id is not None:
                payload[f"{code}_option_id"] = int(option_item.matched_target_id)
        else:
            payload[code] = value
    return payload
