"""Public storefront views (anonymous-accessible)."""

import json

from allianceauth.eveonline.evelinks.eveimageserver import character_portrait_url
from django.db import transaction
from django.http import Http404, HttpResponseBadRequest, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.views.decorators.http import require_POST
from esi.decorators import single_use_token

from ..models import (
    FITTED,
    SECTION_CHOICES,
    BpcListing,
    Buyer,
    FittedListing,
    HullListing,
    ModuleListing,
    Order,
    OrderLine,
    Shop,
)
from ..permissions import can_manage
from ..sections import handler_for
from ..tasks import notify_new_order


def _buyer_context(request):
    """Session-backed buyer identity for storefront templates.

    Returns ``{"buyer": None}`` when anonymous, or the buyer dict augmented with a
    portrait URL derived from the character id (nothing extra is stored in the session).
    """
    buyer = request.session.get("shop_buyer")
    if buyer:
        record = Buyer.objects.filter(character_id=buyer["id"]).first()
        buyer = {
            **buyer,
            "portrait": character_portrait_url(buyer["id"], size=64),
            "discord_handle": record.discord_handle if record else "",
        }
    return {"buyer": buyer}


def _require_visible_shop(request, slug):
    shop = get_object_or_404(Shop, slug=slug)
    if not shop.is_published and not can_manage(request.user, shop):
        raise Http404("Shop not found")
    return shop


@require_POST
def submit_order(request, slug):
    """Persist a client cart as an order. Login-gated; prices re-read server-side."""
    shop = _require_visible_shop(request, slug)
    buyer_session = request.session.get("shop_buyer")
    if not buyer_session:
        return JsonResponse({"error": "login required"}, status=403)
    try:
        payload = json.loads(request.body)
    except (ValueError, TypeError):
        return HttpResponseBadRequest("invalid payload")

    raw_lines = payload.get("lines") or []
    if not raw_lines:
        return HttpResponseBadRequest("empty request")

    resolved = []
    remaining = {}  # (section, listing_id) -> stock left, so duplicate lines can't
    # jointly exceed available for stock-limited sections.
    for raw in raw_lines:
        section = raw.get("section")
        handler = handler_for(section)
        if handler is None or not shop.section_enabled(section):
            return HttpResponseBadRequest("invalid section")
        try:
            qty = int(raw.get("qty", 0))
        except (ValueError, TypeError):
            qty = 0
        if qty < 1:
            return HttpResponseBadRequest("invalid quantity")
        try:
            listing_id = int(raw.get("listing_id"))
        except (ValueError, TypeError):
            return HttpResponseBadRequest("invalid item")
        snap = handler.build_order_line(shop, listing_id)
        if snap is None:
            return HttpResponseBadRequest("item not available")
        available = snap.get("available")
        if available is not None:  # stock-limited section (not build-to-order)
            key = (snap["section"], snap["listing_id"])
            left = remaining.get(key, available)
            qty = min(qty, left)
            if qty < 1:
                continue  # out of stock — drop the line
            remaining[key] = left - qty
        resolved.append((snap, qty))

    if not resolved:
        return HttpResponseBadRequest("out of stock")

    buyer = Buyer.upsert_from_session(
        buyer_session, discord_handle=(payload.get("discord_handle") or "").strip()
    )
    with transaction.atomic():
        order = Order.objects.create(
            shop=shop,
            buyer=buyer,
            additional_info=(payload.get("additional_info") or "").strip(),
        )
        for snap, qty in resolved:
            OrderLine.objects.create(
                order=order,
                section=snap["section"],
                eve_type=snap["eve_type"],
                quantity=qty,
                unit_price=snap["unit_price"],
                listing_id=snap["listing_id"],
                label=snap["label"],
                fit_json=snap.get("fit_json") or {},
            )
    transaction.on_commit(lambda: notify_new_order.apply_async(args=[order.pk]))
    return JsonResponse({"reference": order.reference})


def detail(request, slug):
    """Bare shop URL — forward to the first enabled section (or an empty state)."""
    shop = _require_visible_shop(request, slug)
    section = shop.first_enabled_section()
    if section is None:
        return render(
            request,
            "storefront/_no_sections.html",
            {
                "shop": shop,
                "is_preview": not shop.is_published,
                **_buyer_context(request),
            },
        )
    return redirect("storefront:section", slug=slug, section=section)


def section_view(request, slug, section):
    shop = _require_visible_shop(request, slug)
    if section not in dict(SECTION_CHOICES) or not shop.section_enabled(section):
        raise Http404("Section not found")
    ctx = {
        "shop": shop,
        "section": section,
        "sections": shop.enabled_section_choices(),
        "is_preview": not shop.is_published,
        **_buyer_context(request),
    }
    handler = handler_for(section)
    if handler is not None:
        ctx.update(handler.storefront_context(shop, request))
    return render(request, f"storefront/sections/{section}.html", ctx)


def bpc_detail(request, slug, listing_id):
    shop = _require_visible_shop(request, slug)
    listing = get_object_or_404(
        BpcListing.objects.select_related(
            "eve_type", "eve_type__eve_group"
        ).prefetch_related("variants"),
        pk=listing_id,
        shop=shop,
    )
    return render(
        request,
        "storefront/bpc_detail.html",
        {
            "shop": shop,
            "listing": listing,
            "variants": listing.variants.filter(hidden=False).order_by(
                "-runs", "me", "te"
            ),
            "is_preview": not shop.is_published,
            **_buyer_context(request),
        },
    )


def item_detail(request, slug, listing_id):
    shop = get_object_or_404(Shop, slug=slug)
    if not shop.is_published and not can_manage(request.user, shop):
        raise Http404("Shop not found")
    listing = get_object_or_404(
        HullListing.objects.select_related("eve_type", "eve_type__eve_group"),
        pk=listing_id,
        shop=shop,
    )
    return render(
        request,
        "storefront/item_detail.html",
        {
            "shop": shop,
            "listing": listing,
            "section": "hulls",
            "is_preview": not shop.is_published,
            **_buyer_context(request),
        },
    )


def module_detail(request, slug, listing_id):
    shop = get_object_or_404(Shop, slug=slug)
    if not shop.is_published and not can_manage(request.user, shop):
        raise Http404("Shop not found")
    listing = get_object_or_404(
        ModuleListing.objects.select_related("eve_type", "eve_type__eve_group"),
        pk=listing_id,
        shop=shop,
    )
    return render(
        request,
        "storefront/item_detail.html",
        {
            "shop": shop,
            "listing": listing,
            "section": "modules",
            "is_preview": not shop.is_published,
            **_buyer_context(request),
        },
    )


def fitted_detail(request, slug, listing_id):
    shop = get_object_or_404(Shop, slug=slug)
    if not shop.is_published and not can_manage(request.user, shop):
        raise Http404("Shop not found")
    if not shop.section_enabled(FITTED):
        raise Http404("Section not found")  # section disabled -> hide the fit + EFT
    listing = get_object_or_404(
        FittedListing.objects.select_related("eve_type", "eve_type__eve_group"),
        pk=listing_id,
        shop=shop,
    )
    from ..sections.fitted.reconstruct import fit_display_groups, fit_to_eft

    fit = listing.fit_json or {}
    fit_name = listing.ship_name or listing.eve_type.name
    return render(
        request,
        "storefront/fitted_detail.html",
        {
            "shop": shop,
            "listing": listing,
            "section": "fitted",
            "fit": fit_display_groups(fit),
            "eft": fit_to_eft(listing.eve_type.name, fit_name, fit),
            "is_preview": not shop.is_published,
            **_buyer_context(request),
        },
    )


@single_use_token(scopes="publicData")
def login(request, token, slug):
    """Return leg of EVE SSO: record identity, discard the single-use token.

    django-esi's ``single_use_token`` runs the pre-SSO redirect and code exchange; this
    body only runs once a verified token is in hand. We keep just the character identity
    (no ongoing ESI access), so the throwaway token row is deleted.
    """
    request.session["shop_buyer"] = {
        "id": token.character_id,
        "name": token.character_name,
    }
    token.delete()
    return redirect("storefront:detail", slug=slug)


def logout(request, slug):
    """Clear the session buyer identity and return to the shop."""
    request.session.pop("shop_buyer", None)
    return redirect("storefront:detail", slug=slug)
