# sales/views/api_order.py
import logging
import json
import csv
import string
from datetime import datetime, timedelta
from decimal import Decimal

from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.utils import timezone
from django.db import transaction
from django.db.models import Sum, Count, Q, F
from django.core.exceptions import PermissionDenied

from inventory.models import Product
from core.models import Branch
from core.utils import check_limit_or_block, filter_orders_by_user_branch
from accounts.decorators import role_required
from sales.models import (
    POSOrder, POSOrderItem, PaymentTransaction,
    DailySalesSummary, UnusualTransaction, Customer,
)
from sales.forms import POSOrderForm, POSOrderItemForm

logger = logging.getLogger(__name__)


@login_required
def api_create_order(request):
    """JSON endpoint for creating POS orders (used by offline sync)."""
    try:
        data = json.loads(request.body.decode("utf-8"))
    except json.JSONDecodeError:
        return JsonResponse({"success": False, "error": "Invalid JSON"}, status=400)

    items = data.get("items") or []
    if not items:
        return JsonResponse({"success": False, "error": "No items"}, status=400)

    totals = data.get("totals") or {}
    payment_method = data.get("payment_method") or "cash"
    reference = data.get("reference_number") or ""
    customer_name = data.get("customer_name") or ""
    customer_phone = data.get("customer_phone") or ""
    client_id = data.get("client_id")

    # Split payment fields from payload
    is_split_payment = bool(data.get("is_split_payment", False))
    split_payments_raw = data.get("split_payments") or []  # [{method, amount}, {method, amount}]

    # ── Rate limiting (re-uses existing limit check) ──────────────
    try:
        check_limit_or_block("orders_per_day", request=request)
    except Exception as e:
        return JsonResponse({"success": False, "error": str(e)}, status=429)

    # ── Client-ID deduplication for offline retries ────────────────
    client_id = data.get("client_id") or ""
    company = getattr(request, 'company', None)
    company_settings = getattr(company, 'settings', None)
    unlimited_stock = getattr(company_settings, 'enable_unlimited_stock', False) if company_settings else False

    if client_id:
        existing = POSOrder.objects.filter(client_id=client_id, company=company).first()
        if existing:
            _slug = getattr(company, 'slug', None)
            receipt_path = f"/{_slug}/sales/receipt/{existing.id}/" if _slug else f"/sales/receipt/{existing.id}/"
            return JsonResponse({
                "success": True,
                "order_id": existing.id,
                "order_number": existing.order_number,
                "redirect_url": receipt_path,
                "client_id": client_id,
                "duplicate": True,
            })

    try:
        with transaction.atomic():
            try:
                subtotal = Decimal(str(totals.get("subtotal", 0)))
                tax_amount = Decimal(str(totals.get("tax", totals.get("tax_amount", 0))))
                grand_total = Decimal(
                    str(totals.get("grand_total", totals.get("grandTotal", subtotal + tax_amount)))
                )
            except Exception:
                subtotal = Decimal("0")
                tax_amount = Decimal("0")
                for item in items:
                    price = Decimal(str(item.get("price", 0)))
                    qty = Decimal(str(item.get("quantity") or 1))
                    subtotal += price * qty
                grand_total = subtotal

            # NEW: handle payment status + amount paid
            payment_status = data.get("payment_status") or "full"
            raw_amount_paid = data.get("amount_paid")

            try:
                amount_paid = None
                if raw_amount_paid not in (None, ""):
                    _parsed = Decimal(str(raw_amount_paid))
                    # Treat 0 as "not provided" only when status is full
                    if not (_parsed == Decimal("0") and payment_status == "full"):
                        amount_paid = _parsed
            except Exception:
                amount_paid = None

            # Logic:
            # - full: amount_paid = grand_total (if empty OR 0)
            # - partial/credit: if empty → 0
            if payment_status == "full":
                if amount_paid is None:
                    amount_paid = grand_total
            else:
                if amount_paid is None:
                    amount_paid = Decimal("0")

            if amount_paid < 0:
                amount_paid = Decimal("0")
            if amount_paid > grand_total:
                amount_paid = grand_total

            balance_amount = grand_total - amount_paid

            user_branch = getattr(request.user, "branch", None)
            # company already pulled above

            if user_branch is None and company:
                user_branch = Branch.objects.filter(company=company, is_active=True).first()
            elif user_branch is None:
                user_branch = Branch.objects.filter(is_active=True).first()

            # ── Guaranteed-unique order number ─────────────────────────────
            # Using a 10-char UUID hex suffix eliminates the collision risk that
            # caused intermittent IntegrityError 500s during rapid submissions.
            import uuid as _uuid
            _order_number = f"ORD-{timezone.now().strftime('%m%d')}-{_uuid.uuid4().hex[:10].upper()}"

            # ── Read and apply promo/discount ──────────────────────────
            promo_code_val    = data.get("promo_code", "")
            disc_raw          = data.get("discount_amount", 0)
            try:
                discount_amount = min(Decimal(str(disc_raw)), subtotal)
                if discount_amount < 0:
                    discount_amount = Decimal("0")
            except Exception:
                discount_amount = Decimal("0")

            # Apply discount to grand total (preserving tax)
            grand_total = Decimal(
                str(totals.get("grand_total", totals.get("grandTotal", subtotal + tax_amount)))
            )
            # If client sends discount separately, recalculate final correctly
            if discount_amount > 0:
                grand_total = max(Decimal("0"), subtotal + tax_amount - discount_amount)

            # Now that grand_total is correct, fix amount_paid if it was set as full
            if payment_status == "full":
                amount_paid = grand_total
            balance_amount = max(Decimal("0"), grand_total - amount_paid)

            order = POSOrder.objects.create(
                company=company,
                order_number=_order_number,
                client_id=client_id,
                total_amount=subtotal,
                tax_amount=tax_amount,
                discount_amount=discount_amount,
                final_amount=grand_total,
                payment_method='split' if is_split_payment else payment_method,
                is_split_payment=is_split_payment,
                split_method_1=split_payments_raw[0]['method'] if is_split_payment and len(split_payments_raw) > 0 else '',
                split_method_2=split_payments_raw[1]['method'] if is_split_payment and len(split_payments_raw) > 1 else '',
                status="completed",
                payment_status=payment_status,
                amount_paid=amount_paid,
                balance_amount=balance_amount,
                customer_name=customer_name,
                customer_phone=customer_phone,
                cashier=request.user.username,
                branch=user_branch,
            )

            # ── Create order items & deduct stock ──────────────────────────
            raw_items = (
                data.get("items")
                or data.get("cart")
                or data.get("cartItems")
                or data.get("products")
                or []
            )
            
            for item in raw_items:
                product_id = (
                    item.get("product_id")
                    or item.get("productId")
                    or item.get("id")
                    or item.get("product")
                )
                if not product_id:
                    continue

                qty   = Decimal(str(item.get("quantity") or item.get("qty") or 1))
                price = Decimal(str(item.get("price") or item.get("unit_price") or "0"))
                variant_id = item.get("variant_id") or item.get("variantId")

                # Fetch product
                from inventory.models import BranchStock, ProductVariant, VariantBranchStock
                product = Product.objects.get(id=int(product_id), company=company)

                # Resolve variant if provided
                variant = None
                if variant_id:
                    try:
                        variant = ProductVariant.objects.select_for_update().get(
                            id=int(variant_id), product=product, company=company
                        )
                        # Use variant price if POS didn't send one
                        if price == Decimal('0'):
                            price = variant.effective_price
                    except ProductVariant.DoesNotExist:
                        pass

                item_name = product.name
                if variant:
                    item_name = f"{product.name} ({variant.display_label})"

                # ── Create line item ────────────────────────────────────────────
                oi = POSOrderItem.objects.create(
                    order=order,
                    product_id=product.id,
                    variant=variant,
                    quantity=qty,
                    unit_price=price,
                    total_price=price * qty,
                    product_name=item_name,
                )

                # ── Deduct stock ────────────────────────────────────────────────
                if not unlimited_stock:
                    if variant:
                        if user_branch:
                            vbs, _ = VariantBranchStock.objects.select_for_update().get_or_create(
                                company=company, variant=variant, branch=user_branch,
                                defaults={'quantity': 0}
                            )
                            avail = Decimal(str(vbs.quantity))
                            if avail < qty:
                                raise ValueError(
                                    f"Insufficient stock for '{item_name}' at {user_branch.name}: "
                                    f"only {avail} left."
                                )
                            vbs.quantity = max(Decimal('0'), avail - qty)
                            vbs.save()
                        else:
                            locked_variant = ProductVariant.objects.select_for_update().get(
                                id=variant.id, company=company
                            )
                            avail = Decimal(str(locked_variant.stock_quantity))
                            if avail < qty:
                                raise ValueError(
                                    f"Insufficient stock for '{item_name}': "
                                    f"only {avail} left."
                                )
                            locked_variant.stock_quantity = max(Decimal('0'), avail - qty)
                            locked_variant.save()
                    else:
                        if user_branch:
                            branch_stock, _ = BranchStock.objects.select_for_update().get_or_create(
                                company=company, product=product, branch=user_branch,
                                defaults={'quantity': 0}
                            )
                            avail = Decimal(str(branch_stock.quantity))
                            if avail < qty:
                                raise ValueError(
                                    f"Insufficient stock for '{item_name}' at {user_branch.name}: "
                                    f"only {avail} left."
                                )
                            branch_stock.quantity = max(Decimal('0'), avail - qty)
                            branch_stock.save()
                        else:
                            locked_product = Product.objects.select_for_update().get(
                                id=product.id, company=company
                            )
                            avail = Decimal(str(locked_product.stock_quantity))
                            if avail < qty:
                                raise ValueError(
                                    f"Insufficient stock for '{item_name}': "
                                    f"only {avail} left."
                                )
                            locked_product.stock_quantity = max(Decimal('0'), avail - qty)
                            locked_product.save()
            # Record payment transaction(s)
            import uuid as _uuid_txn
            if is_split_payment and split_payments_raw:
                # Create one PaymentTransaction per split leg
                for leg in split_payments_raw:
                    leg_method = leg.get('method', 'cash')
                    try:
                        leg_amount = Decimal(str(leg.get('amount', 0)))
                    except Exception:
                        leg_amount = Decimal('0')
                    if leg_amount > 0:
                        PaymentTransaction.objects.create(
                            order=order,
                            payment_method=leg_method,
                            amount=leg_amount,
                            reference_number=reference or f"SPLIT-{_uuid_txn.uuid4().hex[:10].upper()}",
                            status='completed',
                        )
            elif amount_paid > 0:
                _txn_ref = reference or f"POS-{_uuid_txn.uuid4().hex[:12].upper()}"
                PaymentTransaction.objects.create(
                    order=order,
                    payment_method=payment_method,
                    amount=amount_paid,
                    reference_number=_txn_ref,
                    status="completed",
                )

    except ValueError as e:
        # Stock / validation conflicts — tell client clearly
        logger.error(f"[api_create_order] ValueError: {e}", exc_info=True)
        return JsonResponse(
            {"success": False, "error": str(e), "code": "STOCK_CONFLICT"},
            status=400,
        )
    except Exception as e:
        # Unexpected error — log the REAL cause so it appears in server logs
        logger.error(f"[api_create_order] Unhandled exception: {type(e).__name__}: {e}", exc_info=True)
        return JsonResponse(
            {"success": False, "error": "Server error", "detail": str(e)}, status=500
        )

    _slug = getattr(getattr(request, 'company', None), 'slug', None)
    receipt_path = f"/{_slug}/sales/receipt/{order.id}/" if _slug else f"/sales/receipt/{order.id}/"
    
    from sales.suspicious import detect_suspicious
    from accounts.audit import log_action
    log_action(request, 'complete_order', target=f'API Order #{order.order_number}')
    detect_suspicious(order)

    # ── Generate Daily Summary ──
    from sales.models import DailySalesSummary
    DailySalesSummary.generate_summary_from_queryset(
        timezone.now().date(), 
        POSOrder.objects.filter(company=request.company, created_at__date=timezone.now().date(), status='completed')
    )

    return JsonResponse(
        {
            "success": True,
            "order_id": order.id,
            "order_number": order.order_number,
            "redirect_url": receipt_path,
            "client_id": client_id,
        }
    )
from django.db.models import Sum, Count, Max  # combined with top-level import


@login_required
@role_required(['admin', 'manager'])
def placeholder_stub():
    pass

@login_required
def validate_promo(request):
    """POST endpoint for grid/classic POS to validate a promo code."""
    import json
    from django.utils import timezone
    from core.models import PromoCode
    
    if request.method != 'POST':
        return JsonResponse({'valid': False, 'message': 'Invalid request method.'}, status=405)
        
    try:
        data = json.loads(request.body.decode('utf-8'))
        code = (data.get('code') or '').strip().upper()
        order_total = Decimal(str(data.get('order_total', '0') or '0'))
    except json.JSONDecodeError:
        return JsonResponse({'valid': False, 'message': 'Invalid JSON.'}, status=400)
        
    if not code:
        return JsonResponse({'valid': False, 'message': 'No code provided.'})
        
    try:
        promo = PromoCode.objects.get(
            code__iexact=code,
            company=request.company,
            is_active=True,
        )
    except PromoCode.DoesNotExist:
        return JsonResponse({'valid': False, 'message': 'Invalid or inactive code.'})
        
    if promo.is_expired:
        return JsonResponse({'valid': False, 'message': 'This promo code has expired.'})
        
    if promo.is_exhausted:
        return JsonResponse({'valid': False, 'message': 'Promo code usage limit reached.'})
        
    if promo.minimum_order_amount and order_total < promo.minimum_order_amount:
        return JsonResponse({
            'valid': False,
            'message': f'Minimum order of {promo.minimum_order_amount} required.'
        })
        
    discount = promo.calculate_discount(order_total)
    
    return JsonResponse({
        'valid': True,
        'message': f'Promo applied: {promo.get_discount_type_display()} off',
        'discount_amount': float(discount),
    })
