"""
WhatsApp checkout orchestration.
Handles: cart confirmation → collect details → Paystack MoMo charge →
         OTP/PIN if needed → order creation → confirmation message.
"""
import uuid
import logging
from decimal import Decimal
from typing import Dict

from django.utils import timezone

from .session import (
    get_cart, clear_cart,
    set_pending, get_pending, clear_pending,
    set_checkout_state, get_checkout_state, clear_checkout_state,
)
from .paystack_charge import (
    initiate_mobile_money_charge,
    submit_otp,
    submit_pin,
    verify_charge_safe,
    detect_provider,
)
from .media import send_whatsapp_text, send_whatsapp_image

logger = logging.getLogger(__name__)


def _compute_whatsapp_totals(items):
    """
    Compute per-vendor delivery for WhatsApp checkout cart lines.
    """
    from vendors.models import Vendor

    item_subtotal = Decimal("0")
    vendor_item_totals: Dict[int, Decimal] = {}
    for item in items:
        qty = int(item.get("quantity", 0))
        price = Decimal(str(item.get("price", 0)))
        line_subtotal = price * qty
        item_subtotal += line_subtotal
        vendor_id = item.get("vendor_id")
        if vendor_id:
            vendor_item_totals[int(vendor_id)] = vendor_item_totals.get(int(vendor_id), Decimal("0")) + line_subtotal

    delivery_total = Decimal("0")
    vendor_breakdown: Dict[int, Dict[str, Decimal]] = {}
    if vendor_item_totals:
        vendors = Vendor.objects.filter(id__in=list(vendor_item_totals.keys()))
        vendor_map = {v.id: v for v in vendors}
        for vendor_id, items_total in vendor_item_totals.items():
            vendor = vendor_map.get(vendor_id)
            fee = Decimal("0")
            if vendor:
                fee = vendor.delivery_fee or Decimal("0")
                threshold = vendor.free_delivery_threshold
                if threshold is not None and items_total >= threshold:
                    fee = Decimal("0")
            delivery_total += fee
            vendor_breakdown[vendor_id] = {
                "items_subtotal": items_total,
                "delivery_fee": fee,
                "vendor_total": items_total + fee,
            }

    grand_total = item_subtotal + delivery_total
    return item_subtotal, delivery_total, grand_total, vendor_breakdown


def format_cart_summary(phone: str) -> str:
    items = get_cart(phone)
    if not items:
        return "Your cart is empty."
    item_subtotal, delivery_total, grand_total, _ = _compute_whatsapp_totals(items)
    lines = ["*Your cart:*"]
    for i, item in enumerate(items, 1):
        lines.append(
            f"{i}. {item['name']} x{item['quantity']} — "
            f"{item['currency']} {item['price'] * item['quantity']:.2f}"
        )
    lines.append(f"\nItems subtotal: GHS {item_subtotal:.2f}")
    lines.append(f"Delivery: GHS {delivery_total:.2f}")
    lines.append(f"*Total: GHS {grand_total:.2f}*")
    return "\n".join(lines)


def start_checkout(phone: str) -> str:
    """Begin checkout — ask for name, then address."""
    items = get_cart(phone)
    if not items:
        return "Your cart is empty. Find something to shop first!"
    set_checkout_state(phone, "awaiting_name")
    return (
        f"{format_cart_summary(phone)}\n\n"
        "To complete your order, I need a few details.\n"
        "What is your full name?"
    )


def process_checkout_step(phone: str, user_text: str) -> str:
    """
    State machine for collecting checkout details.
    States: awaiting_name → awaiting_address →
            awaiting_payment_method → awaiting_momo_confirm →
            awaiting_momo_done (MoMo prompt on phone — verify via API) or
            awaiting_otp (SMS OTP to type here) or awaiting_pin → done
    """
    state_obj = get_checkout_state(phone)
    if not state_obj:
        return None  # not in checkout flow

    state = state_obj["state"]
    data = state_obj.get("data", {})

    if state == "awaiting_name":
        data["customer_name"] = user_text.strip()
        data["customer_email"] = "no-reply@mirjy.com"
        set_checkout_state(phone, "awaiting_address", data)
        return "What is your delivery address? (Include street, area, and city.)"

    elif state == "awaiting_address":
        data["delivery_address"] = user_text.strip()
        set_checkout_state(phone, "awaiting_payment_method", data)
        _, _, total, _ = _compute_whatsapp_totals(get_cart(phone))
        return (
            f"Great. Total is *GHS {total:.2f}*.\n\n"
            "How would you like to pay?\n"
            "1. Mobile Money (pay right here on WhatsApp)\n"
            "2. Other method (I'll send you a secure checkout link)"
        )

    elif state == "awaiting_payment_method":
        t = user_text.strip().lower()
        if any(k in t for k in ("1", "momo", "mobile money", "mtn", "vodafone", "airteltigo")):
            set_checkout_state(phone, "awaiting_momo_confirm", data)
            _, _, total, _ = _compute_whatsapp_totals(get_cart(phone))
            return (
                f"I'll send a Mobile Money payment prompt to *{phone}* "
                f"for *GHS {total:.2f}*.\n\n"
                "Reply *yes* to confirm, or *no* to cancel."
            )
        else:
            clear_checkout_state(phone)
            checkout_url = _build_checkout_url(phone, data)
            return (
                "No problem! For security, please complete your checkout on the ACA website.\n\n"
                f"Your cart has been saved. Use this link:\n{checkout_url}\n\n"
                "Your items will be waiting for you there."
            )

    elif state == "awaiting_momo_confirm":
        t = user_text.strip().lower()
        if t in ("yes", "y", "ok", "okay", "sure", "go ahead", "confirm"):
            return _initiate_momo_payment(phone, data)
        else:
            clear_checkout_state(phone)
            return "Checkout cancelled. Your cart is still saved — type 'checkout' when you're ready."

    elif state == "awaiting_momo_done":
        return _handle_momo_prompt_followup(phone, user_text.strip(), data)

    elif state == "awaiting_otp":
        return _submit_otp_step(phone, user_text.strip(), data)

    elif state == "awaiting_pin":
        return _submit_pin_step(phone, user_text.strip(), data)

    return None  # not handled here


def _initiate_momo_payment(phone: str, data: dict) -> str:
    _, _, total, _ = _compute_whatsapp_totals(get_cart(phone))
    reference = f"ACAWA-{uuid.uuid4().hex[:12].upper()}"
    provider = detect_provider(phone)
    data["reference"] = reference
    data["phone"] = phone

    try:
        charge = initiate_mobile_money_charge(
            phone=phone,
            email="no-reply@mirjy.com",
            amount_ghs=total,
            reference=reference,
            metadata={
                "customer_name": data.get("customer_name"),
                "delivery_address": data.get("delivery_address"),
                "aca_whatsapp": True,
                "wa_phone": phone,
            },
            provider=provider,
        )
    except Exception as exc:
        logger.error("MoMo charge init failed for %s: %s", phone, exc)
        clear_checkout_state(phone)
        checkout_url = _build_checkout_url(phone, data)
        return (
            "Mobile money payment could not be initiated right now. "
            f"Please use this checkout link instead:\n{checkout_url}"
        )

    status = charge.get("status")

    if status == "send_otp":
        data["charge_data"] = charge
        set_checkout_state(phone, "awaiting_otp", data)
        return (
            "Paystack sent a *one-time password (OTP)* by SMS to your phone. "
            "Reply here with *only the numeric code* (no letters)."
        )

    elif status == "send_pin":
        data["charge_data"] = charge
        set_checkout_state(phone, "awaiting_pin", data)
        return "Please enter your Mobile Money PIN to authorise the payment:"

    elif status == "pay_offline":
        data["charge_data"] = charge
        # Separate state so normal chat is not sent to Paystack as an OTP.
        set_checkout_state(phone, "awaiting_momo_done", data)
        return (
            "A Mobile Money payment prompt was sent to your phone. "
            "Approve or decline it there.\n\n"
            "When you're finished, reply *done* and I'll check with Paystack whether "
            "the payment went through. You can also ask *payment status* anytime."
        )

    elif status == "success":
        return _complete_order(phone, data, charge)

    else:
        clear_checkout_state(phone)
        return f"Payment could not be processed (status: {status}). Please try again or use the checkout link."


def _wants_momo_verification(text: str) -> bool:
    """User is asking to check MoMo payment status or said they're done."""
    t = text.lower().strip()
    if t in (
        "done",
        "paid",
        "yes",
        "ok",
        "okay",
        "yep",
        "yeah",
        "finished",
        "completed",
        "confirmed",
        "approved",
    ):
        return True
    keys = (
        "payment",
        "paid",
        "paystack",
        "charge",
        "money",
        "momo",
        "order",
        "status",
        "through",
        "went",
        "received",
        "successful",
        "declined",
        "failed",
        "cancel",  # "did I cancel"
    )
    if any(k in t for k in keys):
        return True
    return "?" in text


def _payment_failed_explanation(tx: dict) -> str:
    status = (tx.get("status") or "").lower()
    if status == "failed":
        return "Paystack shows this payment as *failed* or declined — nothing was charged."
    if status in ("abandoned", "reversed"):
        return "Paystack shows this payment as *cancelled* or reversed — no money was taken."
    if not tx.get("id"):
        return "There is no completed Paystack payment for this checkout attempt yet."
    return f"Paystack status: *{status or 'unknown'}* — the payment did not complete successfully."


def _handle_momo_prompt_followup(phone: str, text: str, data: dict) -> str:
    """
    After pay_offline MoMo prompt: verify with Paystack API instead of guessing.
    Not used for SMS OTP entry (that's awaiting_otp).
    """
    if not _wants_momo_verification(text):
        return (
            "I'm waiting to confirm your Mobile Money payment with Paystack.\n\n"
            "Reply *done* after you respond to the prompt on your phone, or ask "
            "*payment status* / *did my payment go through?* to check.\n"
            "Type *cancel* to stop checkout."
        )

    ref = data.get("reference", "")
    tx = verify_charge_safe(ref)
    if tx is None:
        return (
            "I couldn't reach Paystack to verify that payment. Try again in a moment, "
            "or check your internet connection."
        )

    status = (tx.get("status") or "").lower()
    if status == "success":
        return _complete_order(phone, data, tx)

    if status in ("failed", "abandoned", "reversed"):
        clear_checkout_state(phone)
        return (
            _payment_failed_explanation(tx)
            + "\n\nYour cart is still saved. Type *checkout* when you're ready to pay again."
        )

    if status in ("pending", "ongoing", "processing"):
        return (
            "Paystack still shows this payment as *pending*. "
            "Complete or cancel the prompt on your phone, then reply *done* or *payment status* again."
        )

    if not tx.get("id"):
        clear_checkout_state(phone)
        return (
            "I don't see a completed payment for this attempt. "
            "If you cancelled or declined the MoMo prompt, nothing was charged.\n\n"
            "Your cart is saved — type *checkout* to try again."
        )

    clear_checkout_state(phone)
    return (
        _payment_failed_explanation(tx)
        + "\n\nYour cart is still saved. Type *checkout* to try again."
    )


def _submit_otp_step(phone: str, otp: str, data: dict) -> str:
    """
    Paystack 'send_otp' flow: user must type the SMS OTP (digits only).
    Do not route normal WhatsApp chat here (that was causing false 'Incorrect OTP').
    """
    cleaned = otp.strip()
    if cleaned.isdigit() and 4 <= len(cleaned) <= 8:
        try:
            result = submit_otp(data.get("reference", ""), cleaned)
        except Exception as exc:
            logger.error("OTP submit failed: %s", exc)
            return "Could not verify that OTP. Please try again or type *cancel*."

        status = result.get("status")
        if status == "success":
            return _complete_order(phone, data, result)
        if status == "send_pin":
            set_checkout_state(phone, "awaiting_pin", data)
            return "Please enter your Mobile Money PIN:"
        return (
            "That code wasn't accepted. Check the SMS from Paystack and try again, "
            "or type *cancel* to stop."
        )

    return (
        "Please enter the *OTP* (digits only) from the SMS Paystack sent. "
        "If you only got a *phone prompt* and no SMS code, type *cancel* and run *checkout* again."
    )


def _submit_pin_step(phone: str, pin: str, data: dict) -> str:
    cleaned = pin.strip()
    if not cleaned.isdigit() or len(cleaned) < 4:
        return "Please enter your Mobile Money *PIN* (digits only), or type *cancel*."

    try:
        result = submit_pin(data.get("reference", ""), cleaned)
    except Exception as exc:
        logger.error("PIN submit failed: %s", exc)
        return "Could not verify PIN. Please try again or type *cancel*."

    status = result.get("status")
    if status == "success":
        return _complete_order(phone, data, result)
    return "That PIN wasn't accepted. Please try again or type *cancel*."


def _complete_order(phone: str, data: dict, tx: dict) -> str:
    """Create order, vendor orders, escrow, clear cart, send confirmation."""
    from cart.models import Order, OrderItem, VendorOrder
    from vendors.models import VendorProduct
    from escrow.models import EscrowTransaction
    from escrow.services.escrow_service import _log as escrow_log
    from django.db import transaction as db_transaction

    items = get_cart(phone)
    if not items:
        clear_checkout_state(phone)
        return "Order could not be placed — cart was empty."

    reference = data.get("reference", tx.get("reference", ""))
    items_subtotal, delivery_total, total, vendor_breakdown = _compute_whatsapp_totals(items)

    try:
        with db_transaction.atomic():
            order = Order.objects.create(
                customer_name=data.get("customer_name", "WhatsApp Customer"),
                customer_email="no-reply@mirjy.com",
                customer_phone=phone,
                delivery_address=data.get("delivery_address", ""),
                items_subtotal=items_subtotal,
                delivery_total=delivery_total,
                total_amount=total,
                status="confirmed",
                paystack_reference=reference,
                session_key=f"wa:{phone}",
            )

            vendor_totals: Dict[int, Decimal] = {}
            for item in items:
                product = None
                vendor_id = item.get("vendor_id")
                if vendor_id:
                    try:
                        product = VendorProduct.objects.get(id=item["product_id"])
                        product.reduce_stock(item["quantity"])
                    except VendorProduct.DoesNotExist:
                        pass

                price = Decimal(str(item["price"]))
                subtotal = price * item["quantity"]
                OrderItem.objects.create(
                    order=order,
                    vendor_id=vendor_id,
                    product=product,
                    product_name=item["name"],
                    product_price=price,
                    currency=item.get("currency", "GHS"),
                    quantity=item["quantity"],
                    subtotal=subtotal,
                )
                if vendor_id:
                    vendor_totals[vendor_id] = vendor_totals.get(vendor_id, Decimal("0")) + subtotal

            for vendor_id, items_total in vendor_totals.items():
                vendor_delivery = vendor_breakdown.get(vendor_id, {}).get("delivery_fee", Decimal("0"))
                vtotal = items_total + vendor_delivery
                vo = VendorOrder.objects.create(
                    order=order,
                    vendor_id=vendor_id,
                    items_subtotal=items_total,
                    delivery_fee=vendor_delivery,
                    total_amount=vtotal,
                    status="confirmed",
                )
                from vendors.models import Vendor
                vendor = Vendor.objects.get(id=vendor_id)
                escrow = EscrowTransaction.objects.create(
                    order=order,
                    vendor_order=vo,
                    vendor=vendor,
                    buyer=None,
                    amount=vtotal,
                    currency="GHS",
                    paystack_reference=reference,
                    status=EscrowTransaction.STATUS_HELD,
                )
                escrow.compute_fees()
                escrow.save(update_fields=["platform_fee", "seller_payout"])
                escrow_log(
                    escrow, "payment_received", "", EscrowTransaction.STATUS_HELD,
                    f"WhatsApp MoMo payment confirmed. Ref: {reference}",
                )
    except Exception as exc:
        logger.error("Order creation failed for %s: %s", phone, exc)
        clear_checkout_state(phone)
        return (
            "Payment was received but we had trouble creating your order. "
            "Please contact support@mirjy.com with your payment reference: "
            f"{reference}"
        )

    clear_cart(phone)
    clear_checkout_state(phone)

    # Build confirmation
    lines = [
        f"Order confirmed! Your order number is *{order.order_number}*.",
        f"Total paid: *GHS {total:.2f}*",
        f"Delivering to: {data.get('delivery_address', '')}",
        "",
        "You will receive an SMS when your vendor ships your order.",
        "To track your order, just type your order number here anytime.",
    ]
    return "\n".join(lines)


def _build_checkout_url(phone: str, data: dict) -> str:
    """
    Build a magic checkout link that pre-fills the cart for this phone session.
    The cart is stored in cache under wa:{phone} so when they visit ACA,
    their items will be loaded.
    """
    base_url = getattr(__import__("django.conf", fromlist=["settings"]).settings,
                       "SITE_URL", "https://aca.mirjy.com")
    return f"{base_url}/cart/checkout/?wa_session=wa%3A{phone}"