"""
Cart Plugin public endpoints — no merchant JWT required.
All paths are under /public/cart/... and are registered in v1.py with prefix="".
"""
import logging
from typing import Any, Dict, Optional

from fastapi import APIRouter, Depends, Header, Request, status
from sqlalchemy.orm import Session

from src.core.database import get_db
from src.core.exceptions import APIException, NotFoundError
from src.apps.cart_plugin import crud as cart_crud
from src.apps.cart_plugin import services as cart_services
from src.apps.cart_plugin.schemas.cart_schemas import (
    CartEmailCheckRequest,
    CartEmailCheckResponse,
    CartIframeConfigResponse,
    CartLoginRequest,
    CartPayerTokenResponse,
    CartRegisterRequest,
    CartSessionPublicResponse,
    CartSubmitRequest,
    CartSubmitResponse,
    CreateCartSessionRequest,
    CreateCartSessionResponse,
    ValidateDiscountRequest,
    ValidateDiscountResponse,
)
from src.apps.checkouts.helpers.payer_auth import get_current_payer

router = APIRouter()
logger = logging.getLogger(__name__)

# Per-router limiter keyed by client IP.  The SlowAPIMiddleware registered on
# the app in main.py handles the 429 error responses.
from slowapi import Limiter
from slowapi.util import get_remote_address
_limiter = Limiter(key_func=get_remote_address)


# ─── Create session ───────────────────────────────────────────────────────────

@router.post(
    "/public/cart/sessions",
    response_model=CreateCartSessionResponse,
    status_code=status.HTTP_201_CREATED,
    tags=["CART_PUBLIC"],
)
@_limiter.limit("30/minute")
async def create_cart_session(
    request: Request,
    payload: CreateCartSessionRequest,
    x_public_key: str = Header(..., alias="X-Public-Key"),
    db: Session = Depends(get_db),
):
    """
    Create a new cart session.  Requires X-Public-Key header.
    Rate limited to 30 requests per minute per IP.
    """
    origin = request.headers.get("Origin")
    result = await cart_services.create_cart_session(
        public_key=x_public_key,
        origin=origin,
        request_data=payload,
        db=db,
        request=request,
    )
    return {
        "token": result["token"],
        "status": result["status"],
        "currency": result["currency"],
        "expires_at": result["expires_at"],
        "items": [
            {
                "id": item.id,
                "session_id": item.session_id,
                "external_id": item.external_id,
                "name": item.name,
                "description": item.description,
                "quantity": item.quantity,
                "unit_price": item.unit_price / 100.0,
                "image_url": item.image_url,
                "metadata": item.metadata_,
            }
            for item in result["items"]
        ],
        "subtotal": result["subtotal"],
        "checkout_url": result["checkout_url"],
    }


# ─── Get session (public) ─────────────────────────────────────────────────────

@router.get(
    "/public/cart/sessions/{token}",
    response_model=CartSessionPublicResponse,
    tags=["CART_PUBLIC"],
)
async def get_cart_session(
    token: str,
    db: Session = Depends(get_db),
):
    """Load a cart session for the checkout page."""
    result = await cart_services.get_cart_session_public(token, db)
    return {
        "token": result["token"],
        "status": result["status"],
        "currency": result["currency"],
        "expires_at": result["expires_at"],
        "checkout_mode": result["checkout_mode"],
        "return_url": result["return_url"],
        "items": [
            {
                "id": item.id,
                "session_id": item.session_id,
                "external_id": item.external_id,
                "name": item.name,
                "description": item.description,
                "quantity": item.quantity,
                "unit_price": item.unit_price / 100.0,
                "image_url": item.image_url,
                "metadata": item.metadata_,
            }
            for item in result["items"]
        ],
        "subtotal": result["subtotal"],
        "tip_amount": result["tip_amount"],
        "tax_amount": result["tax_amount"],
        "discount_amount": result["discount_amount"],
        "total": result["total"],
        "discount_code": result["discount_code"],
        "merchant_branding": result["merchant_branding"],
        # MED-04: provider_txn_ref is not included in the public session response.
    }


# ─── Iframe config ────────────────────────────────────────────────────────────

@router.get(
    "/public/cart/sessions/{token}/iframe-config",
    response_model=CartIframeConfigResponse,
    tags=["CART_PUBLIC"],
)
@_limiter.limit("30/minute")  # CRIT-03: prevent iframe config enumeration
async def get_iframe_config(
    request: Request,
    token: str,
    db: Session = Depends(get_db),
):
    """Return the provider iframe configuration for the payment step."""
    result = await cart_services.get_cart_iframe_config(token, db)
    return result


# ─── Validate discount ────────────────────────────────────────────────────────

@router.post(
    "/public/cart/sessions/{token}/validate-discount",
    response_model=ValidateDiscountResponse,
    tags=["CART_PUBLIC"],
)
@_limiter.limit("10/minute")  # CRIT-03: prevent discount code brute-force
async def validate_discount(
    request: Request,
    token: str,
    payload: ValidateDiscountRequest,
    db: Session = Depends(get_db),
):
    """Validate a promo / discount code for the given session."""
    result = await cart_services.validate_discount(token, payload.code, db)
    return result


# ─── Check email ──────────────────────────────────────────────────────────────

@router.post(
    "/public/cart/sessions/{token}/check-email",
    response_model=CartEmailCheckResponse,
    tags=["CART_PUBLIC"],
)
@_limiter.limit("20/minute")
async def check_email(
    request: Request,
    token: str,
    payload: CartEmailCheckRequest,
    db: Session = Depends(get_db),
):
    """Check whether a payer email already has an account on this merchant."""
    session = cart_crud.get_cart_session_by_token(db, token)
    if session is None:
        raise NotFoundError(message="Cart session not found.")

    from sqlalchemy import select as sa_select
    from src.apps.users.models.user import User

    user = db.execute(
        sa_select(User).where(
            User.email == str(payload.email),
            User.merchant_id == session.merchant_id,
            User.user_type == "customer",
            User.deleted_at.is_(None),
        )
    ).scalar_one_or_none()
    return {"exists": user is not None}


# ─── Payer login ──────────────────────────────────────────────────────────────

@router.post(
    "/public/cart/sessions/{token}/login",
    response_model=CartPayerTokenResponse,
    tags=["CART_PUBLIC"],
)
@_limiter.limit("5/minute")
async def login_payer(
    request: Request,
    token: str,
    payload: CartLoginRequest,
    db: Session = Depends(get_db),
):
    """Authenticate a payer and return a short-lived payer token."""
    session = cart_crud.get_cart_session_by_token(db, token)
    if session is None:
        raise NotFoundError(message="Cart session not found.")

    from sqlalchemy import select as sa_select
    from src.apps.users.models.user import User
    import bcrypt

    user = db.execute(
        sa_select(User).where(
            User.email == str(payload.email),
            User.merchant_id == session.merchant_id,
            User.user_type == "customer",
            User.deleted_at.is_(None),
        )
    ).scalar_one_or_none()

    if user is None or not bcrypt.checkpw(
        payload.password.encode("utf-8"), user.hashed_password.encode("utf-8")
    ):
        raise APIException(message="Invalid email or password.", status_code=401)

    from src.apps.checkouts.helpers.payer_auth import create_payer_token

    access_token = create_payer_token(
        user_id=user.id,
        merchant_id=session.merchant_id,
    )
    return {
        "access_token": access_token,
        "token_type": "bearer",
        "customer_id": None,
    }


# ─── Payer register ───────────────────────────────────────────────────────────

@router.post(
    "/public/cart/sessions/{token}/register",
    status_code=status.HTTP_201_CREATED,
    response_model=CartPayerTokenResponse,
    tags=["CART_PUBLIC"],
)
@_limiter.limit("5/minute")
async def register_payer(
    request: Request,
    token: str,
    payload: CartRegisterRequest,
    db: Session = Depends(get_db),
):
    """Register a new payer account and return a payer token."""
    session = cart_crud.get_cart_session_by_token(db, token)
    if session is None:
        raise NotFoundError(message="Cart session not found.")

    from sqlalchemy import select as sa_select
    from src.apps.users.models.user import User
    import bcrypt
    import secrets as _secrets

    # Check for existing account — scoped to this merchant only
    existing = db.execute(
        sa_select(User).where(
            User.email == str(payload.email),
            User.merchant_id == session.merchant_id,
            User.user_type == "customer",
            User.deleted_at.is_(None),
        )
    ).scalar_one_or_none()
    if existing:
        from src.core.exceptions import ConflictError
        raise ConflictError(message="An account with this email already exists. Please log in.")

    hashed = bcrypt.hashpw(payload.password.encode("utf-8"), bcrypt.gensalt()).decode("utf-8")
    base_username = str(payload.email).split("@")[0].lower().replace(".", "_")[:20]
    unique_suffix = _secrets.token_hex(4)
    username = f"{base_username}_{unique_suffix}"

    name_parts = payload.name.split(" ", 1)
    first_name = name_parts[0]
    last_name = name_parts[1] if len(name_parts) > 1 else ""

    user = User(
        email=str(payload.email),
        username=username,
        hashed_password=hashed,
        first_name=first_name,
        last_name=last_name,
        is_active=True,
        user_type="customer",
        merchant_id=session.merchant_id,
    )
    db.add(user)
    db.flush()
    db.commit()

    from src.apps.checkouts.helpers.payer_auth import create_payer_token

    access_token = create_payer_token(
        user_id=user.id,
        merchant_id=session.merchant_id,
    )
    return {
        "access_token": access_token,
        "token_type": "bearer",
        "customer_id": None,
    }


# ─── Submit ───────────────────────────────────────────────────────────────────

@router.post(
    "/public/cart/sessions/{token}/submit",
    response_model=CartSubmitResponse,
    tags=["CART_PUBLIC"],
)
@_limiter.limit("30/minute")
async def submit_cart(
    request: Request,
    token: str,
    payload: CartSubmitRequest,
    db: Session = Depends(get_db),
):
    """
    Submit a cart for payment.  Payer token is optional (guest checkout).
    Rate limited to 30 requests per minute per IP.
    """
    from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
    from fastapi import Request as FastAPIRequest

    # Attempt to extract payer token without requiring it.
    payer: Optional[dict] = None
    auth_header = request.headers.get("Authorization", "")
    if auth_header.startswith("Bearer "):
        bearer_token = auth_header.split(" ", 1)[1]
        try:
            import jwt as pyjwt
            from src.core.config import settings

            raw = pyjwt.decode(
                bearer_token,
                settings.JWT_SECRET_KEY,
                algorithms=[settings.JWT_ALGORITHM],
            )
            if raw.get("user_type") == "payer":
                payer = raw
        except Exception:
            pass  # Guest checkout — no payer token

    result = await cart_services.submit_cart(token, payload, db, payer=payer)
    return result
