from __future__ import annotations
from typing import Dict, List, Optional

from sqlalchemy import select, and_
from sqlalchemy.orm import selectinload

from shared.config import config  # if unused elsewhere, you can remove this import

# ORM models
from models.market import (
    Product, ProductImage, ProductSection,
    Listing, ListingItem,
    NavTop, NavSub,
    ProductSticker, ProductLabel,
    ProductAttribute, ProductNutrition, ProductAllergen,
)
from shared.infrastructure.data_client import fetch_data
from sqlalchemy import func, case


# ---------- helpers ----------
def _regular_price_of(p: Product) -> Optional[float]:
    try:
        return (
            float(p.regular_price)
            if p.regular_price is not None
            else (
                float(p.special_price)
                if p.special_price is not None
                else None
            )
        )
    except Exception:
        return None

# ---------- NAV ----------
async def db_nav(session, market_id=None) -> Dict:
    top_q = select(NavTop).where(NavTop.deleted_at.is_(None))
    if market_id is not None:
        top_q = top_q.where(NavTop.market_id == market_id)
    tops = (await session.execute(top_q)).scalars().all()

    top_ids = [t.id for t in tops]
    if top_ids:
        subs = (await session.execute(
            select(NavSub).where(NavSub.top_id.in_(top_ids), NavSub.deleted_at.is_(None))
        )).scalars().all()
    else:
        subs = []

    subs_by_top: Dict[int, List[Dict]] = {}
    for s in subs:
        sub_name = (s.label or s.slug or "").strip()
        subs_by_top.setdefault(s.top_id, []).append({
            "label": s.label,
            "name": sub_name,  # back-compat for callers expecting "name"
            "slug": s.slug,
            "href": s.href,
        })

    cats: Dict[str, Dict] = {}
    for t in tops:
        top_label = (t.label or t.slug or "").strip()
        cats[top_label] = {
            "label": t.label,
            "name": top_label,  # back-compat
            "slug": t.slug,
            "subs": sorted(subs_by_top.get(t.id, []), key=lambda x: (x["name"] or "").lower()),
        }
    return {"cats": cats}


async def db_product_full(session, slug: str, user_id=0) -> Optional[dict]:

    q = (
        select(Product)
        .where(Product.slug == slug, Product.deleted_at.is_(None))
        .options(
            selectinload(Product.images.and_(ProductImage.deleted_at.is_(None))),
            selectinload(Product.sections.and_(ProductSection.deleted_at.is_(None))),
            selectinload(Product.labels.and_(ProductLabel.deleted_at.is_(None))),
            selectinload(Product.stickers.and_(ProductSticker.deleted_at.is_(None))),
            selectinload(Product.attributes.and_(ProductAttribute.deleted_at.is_(None))),
            selectinload(Product.nutrition.and_(ProductNutrition.deleted_at.is_(None))),
            selectinload(Product.allergens.and_(ProductAllergen.deleted_at.is_(None))),
        )
    )
    result = await session.execute(q)

    p = result.scalars().first()
    if not p:
        return None

    is_liked = False
    if user_id:
        liked_data = await fetch_data("likes", "is-liked", params={
            "user_id": user_id, "target_type": "product", "target_slug": slug,
        }, required=False)
        is_liked = (liked_data or {}).get("liked", False)

    gallery = [
        img.url
        for img in sorted(p.images, key=lambda i: (i.kind or "gallery", i.position or 0))
        if (img.kind or "gallery") == "gallery"
    ]
    embedded = [
        img.url
        for img in sorted(p.images, key=lambda i: i.position or 0)
        if (img.kind or "") == "embedded"
    ]
    all_imgs = [
        img.url
        for img in sorted(p.images, key=lambda i: i.position or 0)
        if (img.kind or "") == "all"
    ]
    return {
        "id": p.id,
        "slug": p.slug,
        "title": p.title,
        "brand": p.brand,
        "image": p.image,
        "description_short": p.description_short,
        "description_html": p.description_html,
        "suma_href": p.suma_href,
        "rrp": float(p.rrp) if p.rrp is not None else None,
        "special_price": float(p.special_price) if p.special_price is not None else None,
        "special_price_raw": p.special_price_raw,
        "special_price_currency": p.special_price_currency,
        "regular_price": _regular_price_of(p),
        "regular_price_raw": p.regular_price_raw,
        "regular_price_currency": p.regular_price_currency,
        "rrp_raw": p.rrp_raw,
        "rrp_currency": p.rrp_currency,
        "price_per_unit_raw": p.price_per_unit_raw,
        "price_per_unit": p.price_per_unit,
        "price_per_unit_currency": p.price_per_unit_currency,
        "oe_list_price": p.oe_list_price,
        "images": gallery,
        "embedded_image_urls": embedded,
        "all_image_urls": all_imgs,
        "sections": [{"title": s.title, "html": s.html} for s in p.sections],
        "stickers": [v.name.strip().lower() for v in p.stickers if v.name],
        "labels": [v.name for v in p.labels if v.name],
        "ean": p.ean,
        "sku": p.sku,
        "unit_size": p.unit_size,
        "pack_size": p.pack_size,
        "case_size_raw": p.case_size_raw,
        "case_size_count": p.case_size_count,
        "case_size_item_qty": p.case_size_item_qty,
        "case_size_item_unit": p.case_size_item_unit,
        "info_table": {a.key: a.value for a in p.attributes if a.key},
        "nutrition": [{"key": n.key, "value": n.value, "unit": n.unit} for n in p.nutrition if n.key],
        "allergens": [{"name": a.name, "contains": a.contains} for a in p.allergens if a.name],
        "is_liked": is_liked,
        "deleted_at": p.deleted_at
    }


async def db_product_full_id(session, id:int, user_id=0) -> Optional[dict]:

    q = (
        select(Product)
        .where(Product.id == id)
        .options(
            selectinload(Product.images.and_(ProductImage.deleted_at.is_(None))),
            selectinload(Product.sections.and_(ProductSection.deleted_at.is_(None))),
            selectinload(Product.labels.and_(ProductLabel.deleted_at.is_(None))),
            selectinload(Product.stickers.and_(ProductSticker.deleted_at.is_(None))),
            selectinload(Product.attributes.and_(ProductAttribute.deleted_at.is_(None))),
            selectinload(Product.nutrition.and_(ProductNutrition.deleted_at.is_(None))),
            selectinload(Product.allergens.and_(ProductAllergen.deleted_at.is_(None))),
        )
    )
    result = await session.execute(q)

    p = result.scalars().first()
    if not p:
        return None

    is_liked = False
    if user_id:
        liked_data = await fetch_data("likes", "is-liked", params={
            "user_id": user_id, "target_type": "product", "target_slug": p.slug,
        }, required=False)
        is_liked = (liked_data or {}).get("liked", False)

    gallery = [
        img.url
        for img in sorted(p.images, key=lambda i: (i.kind or "gallery", i.position or 0))
        if (img.kind or "gallery") == "gallery"
    ]
    embedded = [
        img.url
        for img in sorted(p.images, key=lambda i: i.position or 0)
        if (img.kind or "") == "embedded"
    ]
    all_imgs = [
        img.url
        for img in sorted(p.images, key=lambda i: i.position or 0)
        if (img.kind or "") == "all"
    ]
    return {
        "id": p.id,
        "slug": p.slug,
        "title": p.title,
        "brand": p.brand,
        "image": p.image,
        "description_short": p.description_short,
        "description_html": p.description_html,
        "suma_href": p.suma_href,
        "rrp": float(p.rrp) if p.rrp is not None else None,
        "special_price": float(p.special_price) if p.special_price is not None else None,
        "special_price_raw": p.special_price_raw,
        "special_price_currency": p.special_price_currency,
        "regular_price": _regular_price_of(p),
        "regular_price_raw": p.regular_price_raw,
        "regular_price_currency": p.regular_price_currency,
        "rrp_raw": p.rrp_raw,
        "rrp_currency": p.rrp_currency,
        "price_per_unit_raw": p.price_per_unit_raw,
        "price_per_unit": p.price_per_unit,
        "price_per_unit_currency": p.price_per_unit_currency,
        "oe_list_price": p.oe_list_price,
        "images": gallery,
        "embedded_image_urls": embedded,
        "all_image_urls": all_imgs,
        "sections": [{"title": s.title, "html": s.html} for s in p.sections],
        "stickers": [v.name.strip().lower() for v in p.stickers if v.name],
        "labels": [v.name for v in p.labels if v.name],
        "ean": p.ean,
        "sku": p.sku,
        "unit_size": p.unit_size,
        "pack_size": p.pack_size,
        "case_size_raw": p.case_size_raw,
        "case_size_count": p.case_size_count,
        "case_size_item_qty": p.case_size_item_qty,
        "case_size_item_unit": p.case_size_item_unit,
        "info_table": {a.key: a.value for a in p.attributes if a.key},
        "nutrition": [{"key": n.key, "value": n.value, "unit": n.unit} for n in p.nutrition if n.key],
        "allergens": [{"name": a.name, "contains": a.contains} for a in p.allergens if a.name],
        "is_liked": is_liked,
        "deleted_at": p.deleted_at
    }





# ---------- PRODUCTS LISTING ----------

async def db_products_nocounts(
    session,
    top_slug: str | None,
    sub_slug: str | None,
    selected_brands: Optional[List[str]] = None,
    selected_stickers: Optional[List[str]] = None,
    selected_labels: Optional[List[str]] = None,
    page: int = 1,
    search: Optional[str] = None,
    sort: Optional[str] = None,
    page_size: int = 20,
    liked: bool = None,
    user_id: int=0,
    market_id: int | None = None,
) -> Dict:
    BLOCKED_SLUGS = set((config().get("blacklist", {}).get("product", []) or []))
    base_conditions = []
    if BLOCKED_SLUGS:
        base_conditions.append(
            ~Product.slug.in_(BLOCKED_SLUGS),
          )

    if top_slug:
        q_list_conditions = [
            Listing.deleted_at.is_(None),
            NavTop.deleted_at.is_(None),
            NavTop.slug == top_slug,
            NavSub.deleted_at.is_(None),
            NavSub.slug == sub_slug if sub_slug else Listing.sub_id.is_(None),
        ]
        if market_id is not None:
            q_list_conditions.append(NavTop.market_id == market_id)

        q_list = (
            select(Listing.id)
            .join(NavTop, Listing.top)
            .outerjoin(NavSub, Listing.sub)
            .where(*q_list_conditions)
        )

        listing_id = (await session.execute(q_list)).scalars().first()
        if not listing_id:
            return {"total_pages": 1, "items": []}

        base_conditions.append(Product.slug.in_(
            select(ListingItem.slug).where(ListingItem.listing_id == listing_id, ListingItem.deleted_at.is_(None))
        ))
    elif market_id is not None:
        # Browse all within a specific market: filter products through market's nav hierarchy
        market_product_slugs = (
            select(ListingItem.slug)
            .join(Listing, ListingItem.listing_id == Listing.id)
            .join(NavTop, Listing.top_id == NavTop.id)
            .where(
                ListingItem.deleted_at.is_(None),
                Listing.deleted_at.is_(None),
                NavTop.deleted_at.is_(None),
                NavTop.market_id == market_id,
            )
        )
        base_conditions.append(Product.slug.in_(market_product_slugs))

    base_ids_subq = select(Product.id).where(*base_conditions, Product.deleted_at.is_(None))
    base_ids = (await session.execute(base_ids_subq)).scalars().all()
    
    if not base_ids:
        return {"total_pages": 1, "items": []}
    sel_brands = [(b or "").strip().lower() for b in (selected_brands or []) if (b or "").strip()]
    sel_stickers = [(s or "").strip().lower() for s in (selected_stickers or []) if (s or "").strip()]
    sel_labels = [(l or "").strip().lower() for l in (selected_labels or []) if (l or "").strip()]
    search_q = (search or "").strip().lower()

    filter_conditions = []
    if sel_brands:
        filter_conditions.append(func.lower(Product.brand).in_(sel_brands))
    for sticker_name in sel_stickers:
        filter_conditions.append(
            Product.stickers.any(
                and_(
                    func.lower(ProductSticker.name) == sticker_name,
                    ProductSticker.deleted_at.is_(None)
                )
            )
        )
    for label_name in sel_labels:
        filter_conditions.append(
            Product.labels.any(
                and_(
                    func.lower(ProductLabel.name) == label_name,
                    ProductLabel.deleted_at.is_(None),
                )
            )
        )
    if search_q:
        filter_conditions.append(func.lower(Product.description_short).contains(search_q))
    # Fetch liked slugs from likes service (once)
    liked_slugs_set: set[str] = set()
    if user_id and (liked or True):
        liked_slugs_list = await fetch_data("likes", "liked-slugs", params={
            "user_id": user_id, "target_type": "product",
        }, required=False) or []
        liked_slugs_set = set(liked_slugs_list)

    if liked:
        if not liked_slugs_set:
            return {"total_pages": 1, "items": []}
        filter_conditions.append(Product.slug.in_(liked_slugs_set))

    filtered_count_query = select(func.count(Product.id)).where(Product.id.in_(base_ids), *filter_conditions)
    total_filtered = (await session.execute(filtered_count_query)).scalars().one()
    total_pages = max(1, (total_filtered + page_size - 1) // page_size)
    page = max(1, page)

    q_filtered = select(Product).where(Product.id.in_(base_ids), *filter_conditions).options(
        selectinload(Product.images),
        selectinload(Product.sections),
        selectinload(Product.labels),
        selectinload(Product.stickers),
        selectinload(Product.attributes),
        selectinload(Product.nutrition),
        selectinload(Product.allergens),
    )

    sort_mode = (sort or "az").strip().lower()
    if sort_mode == "az":
        q_filtered = q_filtered.order_by(func.lower(Product.title), Product.slug)
    elif sort_mode == "za":
        q_filtered = q_filtered.order_by(func.lower(Product.title).desc(), Product.slug.desc())
    elif sort_mode in ("price-asc", "price_asc", "price-low", "price-low-high", "low-high", "lo-hi"):
        q_filtered = q_filtered.order_by(
            case((Product.regular_price.is_(None), 1), else_=0),
            Product.regular_price.asc(),
            func.lower(Product.title),
            Product.slug
        )
    elif sort_mode in ("price-desc", "price_desc", "price-high", "price-high-low", "high-low", "hi-lo"):
        q_filtered = q_filtered.order_by(
            case((Product.regular_price.is_(None), 1), else_=0),
            Product.regular_price.desc(),
            func.lower(Product.title),
            Product.slug
        )
    else:
        q_filtered = q_filtered.order_by(func.lower(Product.title), Product.slug)

    offset_val = (page - 1) * page_size
    q_filtered = q_filtered.offset(offset_val).limit(page_size)
    products_page = (await session.execute(q_filtered)).scalars().all()

    items: List[Dict] = []
    for p in products_page:
        is_liked = p.slug in liked_slugs_set
        gallery_imgs = sorted((img for img in p.images), key=lambda i: (i.kind or "gallery", i.position or 0))
        gallery = [img.url for img in gallery_imgs if (img.kind or "gallery") == "gallery"]
        embedded = [img.url for img in sorted(p.images, key=lambda i: i.position or 0) if (img.kind or "") == "embedded"]
        all_imgs = [img.url for img in sorted(p.images, key=lambda i: i.position or 0) if (img.kind or "") == "all"]

        items.append({
            "slug": p.slug,
            "title": p.title,
            "brand": p.brand,
            "description_short": p.description_short,
            "description_html": p.description_html,
            "image": p.image,
            "rrp": float(p.rrp) if p.rrp is not None else None,
            "special_price": float(p.special_price) if p.special_price is not None else None,
            "special_price_raw": p.special_price_raw,
            "special_price_currency": p.special_price_currency,
            "regular_price": _regular_price_of(p),
            "regular_price_raw": p.regular_price_raw,
            "regular_price_currency": p.regular_price_currency,
            "rrp_raw": p.rrp_raw,
            "rrp_currency": p.rrp_currency,
            "price_per_unit_raw": p.price_per_unit_raw,
            "price_per_unit": p.price_per_unit,
            "price_per_unit_currency": p.price_per_unit_currency,    
            "images": gallery,
            "embedded_image_urls": embedded,
            "all_image_urls": all_imgs,
            "sections": [{"title": s.title, "html": s.html} for s in p.sections],
            "labels": [l.name for l in p.labels if l.name],
            "stickers": [s.name.strip().lower() for s in p.stickers if s.name],
            "info_table": {a.key: a.value for a in p.attributes if a.key},
            "nutrition": [{"key": n.key, "value": n.value, "unit": n.unit} for n in p.nutrition if n.key],
            "allergens": [{"name": a.name, "contains": a.contains} for a in p.allergens if a.name],
            "ean": p.ean,
            "sku": p.sku,
            "unit_size": p.unit_size,
            "pack_size": p.pack_size,
            "is_liked": is_liked,
        })

    return {
        "total_pages": total_pages,
        "items": items,
    }


async def db_products_counts(
    session,
    top_slug: str | None,
    sub_slug: str | None,
    search: Optional[str] = None,
    user_id: int=0,
    market_id: int | None = None,
) -> Dict:
    BLOCKED_SLUGS = set((config().get("blacklist", {}).get("product", []) or []))
    base_conditions = []

    if top_slug:
        q_list_conditions = [
            Listing.deleted_at.is_(None),
            Listing.top.has(slug=top_slug),
            Listing.sub.has(slug=sub_slug) if sub_slug else Listing.sub_id.is_(None),
        ]
        if market_id is not None:
            q_list_conditions.append(Listing.top.has(market_id=market_id))
        q_list = select(Listing.id).where(*q_list_conditions)
        listing_id = (await session.execute(q_list)).scalars().first()
        if not listing_id:
            return {
                "brands": [],
                "stickers": [],
                "labels": [],
                "liked_count": 0,
                "search_count": 0,
            }

        listing_slug_subquery = select(ListingItem.slug).where(ListingItem.listing_id == listing_id, ListingItem.deleted_at.is_(None))

        if BLOCKED_SLUGS:
            base_conditions.append(
                and_(
                    Product.slug.in_(listing_slug_subquery),
                    ~Product.slug.in_(BLOCKED_SLUGS),
                )
            )
        else:
            base_conditions.append(Product.slug.in_(listing_slug_subquery))
    else:
        if market_id is not None:
            # Browse all within a specific market
            market_product_slugs = (
                select(ListingItem.slug)
                .join(Listing, ListingItem.listing_id == Listing.id)
                .join(NavTop, Listing.top_id == NavTop.id)
                .where(
                    ListingItem.deleted_at.is_(None),
                    Listing.deleted_at.is_(None),
                    NavTop.deleted_at.is_(None),
                    NavTop.market_id == market_id,
                )
            )
            if BLOCKED_SLUGS:
                base_conditions.append(
                    and_(
                        Product.slug.in_(market_product_slugs),
                        ~Product.slug.in_(BLOCKED_SLUGS),
                    )
                )
            else:
                base_conditions.append(Product.slug.in_(market_product_slugs))
        elif BLOCKED_SLUGS:
            base_conditions.append(~Product.slug.in_(BLOCKED_SLUGS))
    base_ids = (await session.execute(select(Product.id).where(*base_conditions, Product.deleted_at.is_(None)))).scalars().all()
    if base_ids:
        base_products_slugs = (await session.execute(
            select(Product.slug).where(Product.id.in_(base_ids), Product.deleted_at.is_(None))
        )).scalars().all()
        if not base_products_slugs:
            return {
                "brands": [],
                "stickers": [],
                "labels": [],
                "liked_count": 0,
                "search_count": 0,
            }
        base_ids = (await session.execute(
            select(Product.id).where(Product.slug.in_(base_products_slugs), Product.deleted_at.is_(None))
        )).scalars().all()
    else:
        return {
            "brands": [],
            "stickers": [],
            "labels": [],
            "liked_count": 0,
            "search_count": 0,
        }

    brands_list: List[Dict] = []
    stickers_list: List[Dict] = []
    labels_list: List[Dict] = []
    liked_count = 0
    search_count = 0
    if user_id:
        liked_slugs_list = await fetch_data("likes", "liked-slugs", params={
            "user_id": user_id, "target_type": "product",
        }, required=False) or []
        liked_slugs_in_base = set(liked_slugs_list) & set(base_products_slugs)
        liked_count = len(liked_slugs_in_base)
    else:
        liked_count = 0

    # Brand counts
    brand_count_rows = await session.execute(
        select(Product.brand, func.count(Product.id))
        .where(Product.id.in_(base_ids),
                Product.brand.is_not(None),
                func.trim(Product.brand) != "",
                Product.deleted_at.is_(None)
        )
        .group_by(Product.brand)
    )
    for brand_name, count in brand_count_rows:
        brands_list.append({"name": brand_name, "count": count})
    brands_list.sort(key=lambda x: (-x["count"], x["name"].lower()))

    # Sticker counts
    sticker_count_rows = await session.execute(
        select(ProductSticker.name, func.count(ProductSticker.product_id))
        .where(
            ProductSticker.product_id.in_(base_ids),
            ProductSticker.deleted_at.is_(None)
        )
        .group_by(ProductSticker.name)
    )
    for sticker_name, count in sticker_count_rows:
        if sticker_name:
            stickers_list.append({"name": sticker_name.strip().lower(), "count": count})
    stickers_list.sort(key=lambda x: (-x["count"], x["name"]))

    # Label counts
    label_count_rows = await session.execute(
        select(ProductLabel.name, func.count(ProductLabel.product_id))
        .where(
            ProductLabel.product_id.in_(base_ids),
            ProductLabel.deleted_at.is_(None)
        )
        .group_by(ProductLabel.name)
    )
    for label_name, count in label_count_rows:
        if label_name:
            labels_list.append({"name": label_name, "count": count})
    labels_list.sort(key=lambda x: (-x["count"], x["name"]))

    
    # Search count
    search_q = (search or "").strip().lower()
    if search_q:
        search_count = (await session.execute(
            select(func.count(Product.id))
            .where(
                Product.id.in_(base_ids),
                func.lower(Product.description_short).contains(search_q),
                Product.deleted_at.is_(None)
              )
        )).scalars().one()
    else:
        search_count = len(base_ids)

    return {
        "brands": brands_list,
        "stickers": stickers_list,
        "labels": labels_list,
        "liked_count": liked_count,
        "search_count": search_count,
    }

async def db_products(
    session,
    top_slug: str | None,
    sub_slug: str | None,
    selected_brands: Optional[List[str]] = None,
    selected_stickers: Optional[List[str]] = None,
    selected_labels: Optional[List[str]] = None,
    page: int = 1,
    search: Optional[str] = None,
    sort: Optional[str] = None,
    page_size: int = 20,
    liked: bool = None,
    user_id: int=0,
    market_id: int | None = None,
) -> Dict:
      return {
        **(await db_products_nocounts(
            session,
            top_slug=top_slug,
            sub_slug=sub_slug,
            selected_brands=selected_brands,
            selected_stickers=selected_stickers,
            selected_labels=selected_labels,
            page=page,
            search=search,
            sort=sort,
            page_size=page_size,
            liked=liked,
            user_id=user_id,
            market_id=market_id,
        )),
        **(await db_products_counts(
            session,
            top_slug=top_slug,
            sub_slug=sub_slug,
            search=search,
            user_id=user_id,
            market_id=market_id,
        )),
    }


