"""Order placement and status-lifecycle services, with ledger integration."""
from __future__ import annotations

import secrets
from decimal import Decimal

from django.db import transaction as db_transaction
from django.utils import timezone
from rest_framework.exceptions import PermissionDenied, ValidationError

from apps.catalog.models import Product
from apps.ledger import services as ledger

from .models import Order, OrderEvent

_ORD_ALPHABET = 'ABCDEFGHJKMNPQRSTUVWXYZ23456789'

# Allowed status transitions for admins. Users have a stricter subset (below).
_ADMIN_TRANSITIONS = {
    Order.Status.PENDING_REVIEW: {Order.Status.APPROVED, Order.Status.OUT_OF_STOCK,
                                  Order.Status.CANCELLED},
    Order.Status.PAYMENT_UPLOADED: {Order.Status.PAYMENT_CONFIRMED,
                                    Order.Status.PAYMENT_REJECTED, Order.Status.CANCELLED},
    Order.Status.APPROVED: {Order.Status.PAYMENT_CONFIRMED, Order.Status.PAYMENT_REJECTED,
                            Order.Status.CANCELLED},
    Order.Status.PAYMENT_REJECTED: {Order.Status.PAYMENT_CONFIRMED, Order.Status.CANCELLED},
    Order.Status.PAYMENT_CONFIRMED: {Order.Status.IN_FULFILLMENT, Order.Status.CANCELLED},
    Order.Status.IN_FULFILLMENT: {Order.Status.DELIVERED, Order.Status.CANCELLED},
    Order.Status.DELIVERED: {Order.Status.COMPLETED},
    Order.Status.OUT_OF_STOCK: {Order.Status.CANCELLED, Order.Status.APPROVED},
}

# Users may only advance/cancel their own orders in these narrow cases.
_USER_TRANSITIONS = {
    Order.Status.PENDING_REVIEW: {Order.Status.CANCELLED},
    Order.Status.DELIVERED: {Order.Status.COMPLETED},
}


def _generate_order_number() -> str:
    stamp = timezone.now().strftime('%y%m%d')
    suffix = ''.join(secrets.choice(_ORD_ALPHABET) for _ in range(5))
    return f'ORD-{stamp}-{suffix}'


def _snapshot(product: Product, quantity: int) -> dict:
    commission = (product.commission_per_unit() * quantity).quantize(Decimal('0.01'))
    profit = ((product.selling_price - product.cost_price) * quantity).quantize(Decimal('0.01'))
    return {
        'product': product,
        'product_name': product.name,
        'product_code': product.product_code,
        'category': product.category.name if product.category else '',
        'selling_price': product.selling_price,
        'buying_price': product.cost_price,
        'profit': profit,
        'commission': commission,
        'commission_percent': product.commission_percent,
        'vat_amount': Decimal('0.00'),
    }


@db_transaction.atomic
def place_order(*, seller, items: list[dict], customer_name: str = '',
                customer_phone: str = '') -> dict:
    """
    Create one or more order lines under a shared order_number.
    `items` = [{product_id, quantity, selected_size?, selected_color?, notes?}].
    """
    if not items:
        raise ValidationError('At least one item is required')

    order_number = _generate_order_number()
    created, total_commission = [], Decimal('0.00')

    for item in items:
        product = Product.objects.filter(
            pk=item.get('product_id'), status=Product.Status.ACTIVE).first()
        if not product:
            raise ValidationError(f"Product {item.get('product_id')} is unavailable")

        quantity = int(item.get('quantity') or 1)
        if quantity < 1:
            raise ValidationError('Quantity must be at least 1')
        if not product.is_unlimited_stock and product.stock_quantity < quantity:
            raise ValidationError(f'{product.name} is out of stock')

        snap = _snapshot(product, quantity)
        order = Order.objects.create(
            user=seller,
            quantity=quantity,
            selected_size=item.get('selected_size') or '',
            selected_color=item.get('selected_color') or '',
            item_notes=item.get('notes') or '',
            customer_name=customer_name or '',
            customer_phone=customer_phone or '',
            order_number=order_number,
            status=Order.Status.PENDING_REVIEW,
            **snap,
        )
        OrderEvent.objects.create(
            order=order, from_status='', to_status=Order.Status.PENDING_REVIEW,
            note='Order placed', changed_by=seller)

        # Decrement finite stock.
        if not product.is_unlimited_stock:
            product.stock_quantity = max(0, product.stock_quantity - quantity)
            product.save(update_fields=['stock_quantity', 'updated_at'])

        total_commission += snap['commission']
        created.append(order)

    return {
        'order_number': order_number,
        'items': [{'id': o.id, 'product': o.product_name,
                   'commission': o.commission, 'status': o.status} for o in created],
        'total_commission': total_commission,
        'status': Order.Status.PENDING_REVIEW,
    }


@db_transaction.atomic
def transition_status(*, order: Order, new_status: str, actor, note: str = '',
                      is_admin: bool = False) -> Order:
    """Validate and apply a status change, posting commission on completion."""
    order = Order.objects.select_for_update().get(pk=order.pk)
    current = order.status

    if current in Order.TERMINAL:
        raise ValidationError(f'Order is already {current} and cannot change')

    allowed = (_ADMIN_TRANSITIONS if is_admin else _USER_TRANSITIONS).get(current, set())
    if new_status not in allowed:
        raise PermissionDenied(
            f'Cannot move order from {current} to {new_status}')

    order.status = new_status
    if not is_admin and new_status not in (Order.Status.COMPLETED, Order.Status.CANCELLED):
        raise PermissionDenied('Not allowed')
    if is_admin:
        order.reviewed_by = actor

    if new_status == Order.Status.COMPLETED:
        order.completed_at = timezone.now()
        if not order.commission_posted:
            ledger.post_commission(
                user=order.user, amount=order.commission, order_id=order.id,
                memo=f'Sales commission · {order.order_number}')
            order.commission_posted = True
    elif new_status == Order.Status.CANCELLED and order.commission_posted:
        ledger.reverse_commission(
            user=order.user, amount=order.commission, order_id=order.id)
        order.commission_posted = False

    order.save()
    OrderEvent.objects.create(
        order=order, from_status=current, to_status=new_status,
        note=note or '', changed_by=actor)
    return order
