"""
E-commerce checkout custom API for DoItEasy Shop.

Provides endpoints for:
- Creating a Stripe checkout session from the client's cart
- Verifying payment status after returning from Stripe
- Finalizing order state and generating order_items records
"""

import json
import logging
import os
import secrets
import string
from datetime import datetime
from typing import List, Optional

import stripe
from fastapi import APIRouter, Depends, HTTPException, Request
from pydantic import BaseModel
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from core.database import get_db
from dependencies.auth import get_current_user
from models.order_items import Order_items
from models.orders import Orders
from models.products import Products
from schemas.auth import UserResponse

logger = logging.getLogger(__name__)

# Configure Stripe API key from environment
stripe.api_key = os.environ.get("STRIPE_SECRET_KEY", "")

router = APIRouter(prefix="/api/v1/ecom", tags=["ecommerce"])


# ---------- Schemas ----------
class CartItemIn(BaseModel):
    product_id: int
    quantity: int
    variant_label: Optional[str] = ""


class ShippingAddress(BaseModel):
    full_name: str
    line1: str
    line2: Optional[str] = ""
    city: str
    state: Optional[str] = ""
    postal_code: str
    country: str
    phone: Optional[str] = ""


class CheckoutRequest(BaseModel):
    items: List[CartItemIn]
    success_url: str
    cancel_url: str
    billing_email: str
    shipping_address: Optional[ShippingAddress] = None
    notes: Optional[str] = ""


class CheckoutResponse(BaseModel):
    session_id: str
    url: str
    order_id: int
    order_number: str


class VerifyRequest(BaseModel):
    session_id: str


class OrderItemOut(BaseModel):
    product_id: int
    product_name: str
    product_type: str
    variant_label: str
    unit_price: float
    quantity: int
    line_total: float
    download_url: str = ""
    license_key: str = ""


class VerifyResponse(BaseModel):
    status: str
    payment_status: str
    order_id: int
    order_number: str
    total: float
    currency: str
    items: List[OrderItemOut]


# ---------- Helpers ----------
def _generate_order_number() -> str:
    """Build a compact human-friendly order number."""
    ts = datetime.utcnow().strftime("%y%m%d%H%M%S")
    suffix = "".join(secrets.choice(string.ascii_uppercase + string.digits) for _ in range(4))
    return f"DE-{ts}-{suffix}"


def _generate_license_key() -> str:
    """Generate a simple license key for digital product deliveries."""
    chunks = []
    alphabet = string.ascii_uppercase + string.digits
    for _ in range(4):
        chunks.append("".join(secrets.choice(alphabet) for _ in range(5)))
    return "-".join(chunks)


def _calc_shipping(physical_subtotal: float, has_physical: bool) -> float:
    """Simple flat-rate shipping; free over $75."""
    if not has_physical:
        return 0.0
    if physical_subtotal >= 75.0:
        return 0.0
    return 7.99


# ---------- Endpoints ----------
@router.post("/create_payment_session", response_model=CheckoutResponse)
async def create_payment_session(
    data: CheckoutRequest,
    request: Request,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Validate cart, create a pending order, and open a Stripe checkout session."""
    if not stripe.api_key:
        raise HTTPException(status_code=500, detail="Stripe is not configured on the server.")
    if not data.items:
        raise HTTPException(status_code=400, detail="Cart is empty.")

    # Load products referenced in the cart
    product_ids = [item.product_id for item in data.items]
    result = await db.execute(select(Products).where(Products.id.in_(product_ids)))
    products = {p.id: p for p in result.scalars().all()}

    # Build line items and totals
    line_items = []
    items_snapshot = []
    subtotal = 0.0
    has_physical = False
    physical_subtotal = 0.0

    for item in data.items:
        product = products.get(item.product_id)
        if product is None or not product.is_active:
            raise HTTPException(status_code=400, detail=f"Product {item.product_id} is unavailable.")
        if item.quantity <= 0:
            raise HTTPException(status_code=400, detail=f"Invalid quantity for {product.name}.")
        if product.stock is not None and product.stock >= 0 and item.quantity > product.stock:
            raise HTTPException(status_code=400, detail=f"Insufficient stock for {product.name}.")

        unit_price = float(product.price or 0)
        line_total = unit_price * item.quantity
        subtotal += line_total

        if product.product_type == "physical":
            has_physical = True
            physical_subtotal += line_total

        description_parts = [product.description or ""]
        if item.variant_label:
            description_parts.append(item.variant_label)
        stripe_description = " | ".join([p for p in description_parts if p])[:500] or "DoItEasy Shop item"

        line_items.append({
            "price_data": {
                # Saudi Riyal is the primary storefront currency. We force "sar"
                # here so Stripe renders totals in SAR regardless of legacy
                # per-product currency fields on older rows.
                "currency": "sar",
                "product_data": {
                    "name": f"{product.name}{' (' + item.variant_label + ')' if item.variant_label else ''}",
                    "description": stripe_description,
                },
                "unit_amount": int(round(unit_price * 100)),
            },
            "quantity": item.quantity,
        })
        items_snapshot.append({
            "product_id": product.id,
            "product_name": product.name,
            "product_type": product.product_type,
            "variant_label": item.variant_label or "",
            "unit_price": unit_price,
            "quantity": item.quantity,
            "line_total": line_total,
            "image_url": product.image_url or "",
            "download_url": product.download_url or "",
        })

    shipping_cost = _calc_shipping(physical_subtotal, has_physical)
    tax = round(subtotal * 0.0, 2)  # Tax disabled for Phase 1
    total = round(subtotal + shipping_cost + tax, 2)
    order_number = _generate_order_number()
    # SAR is the primary (and only) storefront currency.
    currency = "SAR"

    # Add shipping line item if any
    if shipping_cost > 0:
        line_items.append({
            "price_data": {
                "currency": currency.lower(),
                "product_data": {"name": "Shipping"},
                "unit_amount": int(round(shipping_cost * 100)),
            },
            "quantity": 1,
        })

    # Create pending order BEFORE Stripe session so we can link them
    shipping_address_json = ""
    if data.shipping_address is not None:
        shipping_address_json = json.dumps(data.shipping_address.dict())

    new_order = Orders(
        user_id=current_user.id,
        order_number=order_number,
        status="pending",
        payment_status="pending",
        subtotal=subtotal,
        shipping_cost=shipping_cost,
        tax=tax,
        total=total,
        currency=currency,
        items_json=json.dumps(items_snapshot),
        shipping_address_json=shipping_address_json,
        billing_email=data.billing_email,
        notes=data.notes or "",
    )
    db.add(new_order)
    await db.flush()
    await db.commit()
    await db.refresh(new_order)

    # Create Stripe checkout session
    frontend_host = request.headers.get("App-Host") or request.headers.get("Origin")
    if frontend_host and not frontend_host.startswith(("http://", "https://")):
        frontend_host = f"https://{frontend_host}"

    success_url = data.success_url
    cancel_url = data.cancel_url
    if frontend_host:
        if success_url.startswith("/"):
            success_url = f"{frontend_host}{success_url}"
        if cancel_url.startswith("/"):
            cancel_url = f"{frontend_host}{cancel_url}"

    try:
        session = stripe.checkout.Session.create(
            payment_method_types=["card"],
            line_items=line_items,
            mode="payment",
            success_url=success_url,
            cancel_url=cancel_url,
            customer_email=data.billing_email,
            metadata={
                "order_id": str(new_order.id),
                "user_id": current_user.id,
                "order_number": order_number,
            },
        )
    except Exception as e:
        logger.exception("Stripe session creation failed")
        raise HTTPException(status_code=500, detail=f"Payment session creation failed: {str(e)}")

    # Persist session id on order (do not touch created_at/updated_at)
    new_order.stripe_session_id = session.id
    await db.commit()
    await db.refresh(new_order)

    return CheckoutResponse(
        session_id=session.id,
        url=session.url,
        order_id=new_order.id,
        order_number=order_number,
    )


@router.post("/verify_payment", response_model=VerifyResponse)
async def verify_payment(
    data: VerifyRequest,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Verify payment status with Stripe and finalize the order + order_items."""
    if not stripe.api_key:
        raise HTTPException(status_code=500, detail="Stripe is not configured on the server.")

    try:
        session = stripe.checkout.Session.retrieve(data.session_id)
    except Exception as e:
        logger.exception("Stripe session retrieve failed")
        raise HTTPException(status_code=500, detail=f"Failed to retrieve Stripe session: {str(e)}")

    order_id = int((session.metadata or {}).get("order_id", 0))
    if not order_id:
        raise HTTPException(status_code=404, detail="Order reference missing on Stripe session.")

    order_result = await db.execute(select(Orders).where(Orders.id == order_id))
    order = order_result.scalar_one_or_none()
    if order is None:
        raise HTTPException(status_code=404, detail="Order not found.")
    if order.user_id != current_user.id:
        raise HTTPException(status_code=403, detail="You do not own this order.")

    status_mapping = {"complete": "paid", "open": "pending", "expired": "cancelled"}
    payment_status = status_mapping.get(session.status, "pending")

    # Only materialize order_items on the first successful verification
    items_out: List[OrderItemOut] = []
    if payment_status == "paid" and order.payment_status != "paid":
        order.payment_status = "paid"
        order.status = "processing"
        order.payment_method = "stripe"

        try:
            snapshot = json.loads(order.items_json or "[]")
        except Exception:
            snapshot = []

        for snap in snapshot:
            license_key = _generate_license_key() if snap.get("product_type") == "digital" else ""
            oi = Order_items(
                user_id=current_user.id,
                order_id=order.id,
                product_id=snap.get("product_id", 0),
                product_name=snap.get("product_name", ""),
                product_type=snap.get("product_type", ""),
                variant_label=snap.get("variant_label", ""),
                unit_price=float(snap.get("unit_price", 0)),
                quantity=int(snap.get("quantity", 1)),
                line_total=float(snap.get("line_total", 0)),
                download_url=snap.get("download_url", ""),
                license_key=license_key,
            )
            db.add(oi)

            items_out.append(OrderItemOut(
                product_id=snap.get("product_id", 0),
                product_name=snap.get("product_name", ""),
                product_type=snap.get("product_type", ""),
                variant_label=snap.get("variant_label", ""),
                unit_price=float(snap.get("unit_price", 0)),
                quantity=int(snap.get("quantity", 1)),
                line_total=float(snap.get("line_total", 0)),
                download_url=snap.get("download_url", ""),
                license_key=license_key,
            ))

            # Decrement stock for physical products (best-effort)
            if snap.get("product_type") == "physical":
                prod_res = await db.execute(select(Products).where(Products.id == snap.get("product_id")))
                prod = prod_res.scalar_one_or_none()
                if prod is not None and prod.stock is not None and prod.stock > 0:
                    prod.stock = max(0, prod.stock - int(snap.get("quantity", 1)))

        await db.commit()
        await db.refresh(order)
    else:
        # Return items from already-materialized order_items
        oi_res = await db.execute(select(Order_items).where(Order_items.order_id == order.id))
        for oi in oi_res.scalars().all():
            items_out.append(OrderItemOut(
                product_id=oi.product_id,
                product_name=oi.product_name or "",
                product_type=oi.product_type or "",
                variant_label=oi.variant_label or "",
                unit_price=float(oi.unit_price or 0),
                quantity=int(oi.quantity or 0),
                line_total=float(oi.line_total or 0),
                download_url=oi.download_url or "",
                license_key=oi.license_key or "",
            ))

    return VerifyResponse(
        status=order.status,
        payment_status=order.payment_status,
        order_id=order.id,
        order_number=order.order_number,
        total=float(order.total or 0),
        currency=order.currency or "SAR",
        items=items_out,
    )


@router.get("/my_orders_with_items")
async def my_orders_with_items(
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
    limit: int = 50,
):
    """Return the current user's orders with their order_items nested."""
    orders_res = await db.execute(
        select(Orders).where(Orders.user_id == current_user.id).order_by(Orders.id.desc()).limit(limit)
    )
    orders = orders_res.scalars().all()
    out = []
    for o in orders:
        oi_res = await db.execute(select(Order_items).where(Order_items.order_id == o.id))
        items = []
        for oi in oi_res.scalars().all():
            items.append({
                "product_id": oi.product_id,
                "product_name": oi.product_name,
                "product_type": oi.product_type,
                "variant_label": oi.variant_label,
                "unit_price": float(oi.unit_price or 0),
                "quantity": int(oi.quantity or 0),
                "line_total": float(oi.line_total or 0),
                "download_url": oi.download_url or "",
                "license_key": oi.license_key or "",
            })
        out.append({
            "id": o.id,
            "order_number": o.order_number,
            "status": o.status,
            "payment_status": o.payment_status,
            "subtotal": float(o.subtotal or 0),
            "shipping_cost": float(o.shipping_cost or 0),
            "tax": float(o.tax or 0),
            "total": float(o.total or 0),
            "currency": o.currency or "SAR",
            "billing_email": o.billing_email or "",
            "tracking_number": o.tracking_number or "",
            "notes": o.notes or "",
            "created_at": o.created_at.isoformat() if getattr(o, "created_at", None) else "",
            "items": items,
        })
    return {"items": out, "total": len(out)}