"""
PayPal checkout custom API for DoItEasy Shop.

Mirrors the Stripe flow in `ecom_checkout.py`:
- POST /api/v1/paypal/create_order       -> create DB order + PayPal order, return approve URL
- POST /api/v1/paypal/capture_order      -> capture PayPal order, finalize DB order + items
- GET  /api/v1/paypal/config             -> expose client-safe config (client id, mode)

PayPal credentials are pulled from environment variables:
    PAYPAL_CLIENT_ID
    PAYPAL_CLIENT_SECRET
    PAYPAL_MODE            # "sandbox" or "live" (defaults to "sandbox")
"""

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

import httpx
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__)

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


# ---------- Config helpers ----------
def _paypal_mode() -> str:
    mode = (os.environ.get("PAYPAL_MODE") or "sandbox").strip().lower()
    if mode not in ("sandbox", "live"):
        mode = "sandbox"
    return mode


def _paypal_base_url() -> str:
    return (
        "https://api-m.paypal.com"
        if _paypal_mode() == "live"
        else "https://api-m.sandbox.paypal.com"
    )


def _paypal_credentials() -> tuple[str, str]:
    client_id = os.environ.get("PAYPAL_CLIENT_ID", "").strip()
    client_secret = os.environ.get("PAYPAL_CLIENT_SECRET", "").strip()
    if not client_id or not client_secret:
        raise HTTPException(
            status_code=500,
            detail="PayPal is not configured on the server (missing CLIENT_ID / CLIENT_SECRET).",
        )
    return client_id, client_secret


async def _paypal_access_token() -> str:
    """Fetch an OAuth2 access token from PayPal."""
    client_id, client_secret = _paypal_credentials()
    url = f"{_paypal_base_url()}/v1/oauth2/token"
    async with httpx.AsyncClient(timeout=20.0) as http:
        res = await http.post(
            url,
            auth=(client_id, client_secret),
            data={"grant_type": "client_credentials"},
            headers={"Accept": "application/json", "Accept-Language": "en_US"},
        )
    if res.status_code != 200:
        logger.error("PayPal oauth failed: %s %s", res.status_code, res.text)
        raise HTTPException(status_code=500, detail="Failed to authenticate with PayPal.")
    try:
        return res.json()["access_token"]
    except Exception as e:
        logger.exception("PayPal oauth parse failed: %s", e)
        raise HTTPException(status_code=500, detail="Invalid PayPal auth response.")


# ---------- Schemas (mirror ecom_checkout.py) ----------
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 PayPalCreateRequest(BaseModel):
    items: List[CartItemIn]
    return_url: str
    cancel_url: str
    billing_email: str
    shipping_address: Optional[ShippingAddress] = None
    notes: Optional[str] = ""


class PayPalCreateResponse(BaseModel):
    paypal_order_id: str
    approve_url: str
    order_id: int
    order_number: str


class PayPalCaptureRequest(BaseModel):
    paypal_order_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 PayPalCaptureResponse(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:
    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:
    alphabet = string.ascii_uppercase + string.digits
    return "-".join("".join(secrets.choice(alphabet) for _ in range(5)) for _ in range(4))


def _calc_shipping(physical_subtotal: float, has_physical: bool) -> float:
    if not has_physical:
        return 0.0
    if physical_subtotal >= 75.0:
        return 0.0
    return 7.99


async def _notify_whatsapp_stub(order: Orders) -> None:
    """Stub trigger for post-payment WhatsApp auto-provisioning.

    Intentionally best-effort: any failure is swallowed so it never blocks payment.
    This is a placeholder that can later be wired to the full WhatsApp provisioning
    pipeline.
    """
    try:
        logger.info(
            "[whatsapp_provision_stub] order_id=%s order_number=%s user_id=%s total=%s currency=%s",
            order.id,
            order.order_number,
            order.user_id,
            order.total,
            order.currency,
        )
    except Exception:
        # Never let telemetry break the payment flow.
        pass


# ---------- Endpoints ----------
@router.get("/config")
async def paypal_config():
    """Return client-safe PayPal config so the frontend can render the approval button."""
    client_id = os.environ.get("PAYPAL_CLIENT_ID", "").strip()
    return {
        "client_id": client_id,
        "mode": _paypal_mode(),
        "configured": bool(client_id and os.environ.get("PAYPAL_CLIENT_SECRET")),
    }


@router.post("/create_order", response_model=PayPalCreateResponse)
async def create_paypal_order(
    data: PayPalCreateRequest,
    request: Request,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Validate cart, create a pending DB order, then create a PayPal order and return the approval URL."""
    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()}

    subtotal = 0.0
    has_physical = False
    physical_subtotal = 0.0
    items_snapshot = []
    paypal_items = []

    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

        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 "",
        })

        display_name = f"{product.name}"
        if item.variant_label:
            display_name = f"{display_name} ({item.variant_label})"
        paypal_items.append({
            "name": display_name[:127],
            "quantity": str(int(item.quantity)),
            "unit_amount": {
                "currency_code": "SAR",
                "value": f"{unit_price:.2f}",
            },
            "category": "DIGITAL_GOODS" if product.product_type == "digital" else "PHYSICAL_GOODS",
        })

    shipping_cost = _calc_shipping(physical_subtotal, has_physical)
    tax = 0.0
    total = round(subtotal + shipping_cost + tax, 2)
    order_number = _generate_order_number()
    currency = "SAR"

    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 "",
        payment_method="paypal",
    )
    db.add(new_order)
    await db.flush()
    await db.commit()
    await db.refresh(new_order)

    # Resolve return/cancel URLs against frontend host if relative
    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}"

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

    # Build PayPal order body
    purchase_unit = {
        "reference_id": str(new_order.id),
        "description": f"DoItEasy Shop Order {order_number}"[:127],
        "custom_id": str(new_order.id),
        "invoice_id": order_number,
        "amount": {
            "currency_code": "SAR",
            "value": f"{total:.2f}",
            "breakdown": {
                "item_total": {"currency_code": "SAR", "value": f"{subtotal:.2f}"},
                "shipping": {"currency_code": "SAR", "value": f"{shipping_cost:.2f}"},
                "tax_total": {"currency_code": "SAR", "value": f"{tax:.2f}"},
            },
        },
        "items": paypal_items,
    }

    body = {
        "intent": "CAPTURE",
        "purchase_units": [purchase_unit],
        "application_context": {
            "brand_name": "DoItEasy",
            "landing_page": "LOGIN",
            "user_action": "PAY_NOW",
            "return_url": return_url,
            "cancel_url": cancel_url,
            "shipping_preference": "NO_SHIPPING" if not has_physical else "GET_FROM_FILE",
        },
    }

    access_token = await _paypal_access_token()
    try:
        async with httpx.AsyncClient(timeout=25.0) as http:
            res = await http.post(
                f"{_paypal_base_url()}/v2/checkout/orders",
                headers={
                    "Authorization": f"Bearer {access_token}",
                    "Content-Type": "application/json",
                    "Prefer": "return=representation",
                },
                json=body,
            )
    except Exception as e:
        logger.exception("PayPal create order HTTP failed")
        raise HTTPException(status_code=502, detail=f"PayPal network error: {e}")

    if res.status_code not in (200, 201):
        logger.error("PayPal create order failed: %s %s", res.status_code, res.text)
        raise HTTPException(
            status_code=502,
            detail="PayPal failed to create order. Please try again or pick another method.",
        )

    try:
        pp = res.json()
        pp_id = pp["id"]
        approve_url = ""
        for link in pp.get("links") or []:
            if link.get("rel") == "approve":
                approve_url = link.get("href") or ""
                break
        if not approve_url:
            raise ValueError("No approve link in PayPal response")
    except Exception as e:
        logger.exception("PayPal create order parse failed")
        raise HTTPException(status_code=502, detail=f"Invalid PayPal create order response: {e}")

    # Persist PayPal id on the same stripe_session_id column (reused as external payment ref)
    new_order.stripe_session_id = f"paypal:{pp_id}"
    await db.commit()
    await db.refresh(new_order)

    return PayPalCreateResponse(
        paypal_order_id=pp_id,
        approve_url=approve_url,
        order_id=new_order.id,
        order_number=order_number,
    )


@router.post("/capture_order", response_model=PayPalCaptureResponse)
async def capture_paypal_order(
    data: PayPalCaptureRequest,
    current_user: UserResponse = Depends(get_current_user),
    db: AsyncSession = Depends(get_db),
):
    """Capture an approved PayPal order and finalize the DB order + order_items."""
    if not data.paypal_order_id:
        raise HTTPException(status_code=400, detail="Missing PayPal order id.")

    # Look up order first via the stored reference
    ref = f"paypal:{data.paypal_order_id}"
    order_res = await db.execute(select(Orders).where(Orders.stripe_session_id == ref))
    order = order_res.scalar_one_or_none()
    if order is None:
        raise HTTPException(status_code=404, detail="Order not found for this PayPal session.")
    if order.user_id != current_user.id:
        raise HTTPException(status_code=403, detail="You do not own this order.")

    # Capture with PayPal
    access_token = await _paypal_access_token()
    try:
        async with httpx.AsyncClient(timeout=25.0) as http:
            res = await http.post(
                f"{_paypal_base_url()}/v2/checkout/orders/{data.paypal_order_id}/capture",
                headers={
                    "Authorization": f"Bearer {access_token}",
                    "Content-Type": "application/json",
                    "Prefer": "return=representation",
                },
                json={},
            )
    except Exception as e:
        logger.exception("PayPal capture HTTP failed")
        raise HTTPException(status_code=502, detail=f"PayPal network error: {e}")

    # 422 with ORDER_ALREADY_CAPTURED is an acceptable success path on retry
    already_captured = False
    if res.status_code == 422:
        try:
            body = res.json()
            issues = body.get("details") or []
            for it in issues:
                if (it.get("issue") or "").upper() == "ORDER_ALREADY_CAPTURED":
                    already_captured = True
                    break
        except Exception:
            pass

    if res.status_code not in (200, 201) and not already_captured:
        logger.error("PayPal capture failed: %s %s", res.status_code, res.text)
        raise HTTPException(
            status_code=502,
            detail="PayPal failed to capture payment. Please try again.",
        )

    paid = True
    if not already_captured:
        try:
            pp = res.json()
            status = (pp.get("status") or "").upper()
            paid = status == "COMPLETED"
        except Exception:
            paid = False

    items_out: List[OrderItemOut] = []
    if paid and order.payment_status != "paid":
        order.payment_status = "paid"
        order.status = "processing"
        order.payment_method = "paypal"

        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,
            ))

            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)

        # Post-payment stub — never block success on this
        await _notify_whatsapp_stub(order)
    else:
        # Load previously materialized 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 PayPalCaptureResponse(
        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,
    )