from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.core.exceptions import PermissionDenied
from django.db import transaction
from django.db.models import DecimalField, F, Q, Sum, Value
from django.db.models.functions import Coalesce
from .models import (
    Asset,
    Branch,
    Product,
    Category,
    ProductBatch,
    PurchaseReturn,
    StockAdjustmentRequest,
    StockLocation,
    StockMovement,
    StockTransfer,
    Supplier,
    PurchaseOrder,
    UserStoreAssignment,
)
from .forms import (
    AssetAssignmentForm,
    AssetForm,
    AssetMaintenanceForm,
    BranchForm,
    CategoryForm,
    ProductForm,
    StockAdjustmentForm,
    StockAdjustmentRequestForm,
    StockLocationForm,
    StockTransferForm,
    StockTransferItemFormSet,
    PurchaseReturnForm,
    PurchaseReturnItemFormSet,
)
from .utils import add_stock, deduct_stock
from core.decorators import capability_required
from core.permissions import Capability
from django.core.paginator import Paginator
from django.utils import timezone
from .forms import SupplierForm, PurchaseOrderForm, PurchaseOrderItemFormSet
from finance.models import AccountTransaction
from core.utils import export_csv


def _active_branches():
    return Branch.objects.filter(is_active=True).order_by('name')


def _parse_branch_filter(request):
    branch_value = (request.GET.get('branch') or '').strip()
    if not branch_value:
        return '', None
    try:
        branch_id = int(branch_value)
    except (TypeError, ValueError):
        return '', None
    branch = Branch.objects.filter(pk=branch_id, is_active=True).first()
    if not branch:
        return '', None
    return str(branch.id), branch


def _storekeeper_assignment(request):
    if not getattr(request.user, 'is_authenticated', False):
        return None
    if not request.user.is_storekeeper:
        return None
    if hasattr(request, '_storekeeper_assignment'):
        return request._storekeeper_assignment
    request._storekeeper_assignment = (
        UserStoreAssignment.objects.select_related('location__branch')
        .filter(user=request.user, is_active=True)
        .first()
    )
    return request._storekeeper_assignment


def _notify_missing_store_assignment(request):
    if getattr(request, '_missing_store_assignment_notified', False):
        return
    messages.error(
        request,
        'No active store assignment found. Contact administrator to assign your store location.',
    )
    request._missing_store_assignment_notified = True


def _resolve_branch_scope(request):
    requested_value, requested_branch = _parse_branch_filter(request)
    if not request.user.is_storekeeper:
        return requested_value, requested_branch, False, False

    assignment = _storekeeper_assignment(request)
    if not assignment or not assignment.location_id or not assignment.location.branch_id:
        return '', None, True, True

    forced_branch = assignment.location.branch
    return str(forced_branch.id), forced_branch, True, False


def _branch_options_for_request(request, scoped_branch=None):
    if request.user.is_storekeeper:
        if scoped_branch:
            return Branch.objects.filter(pk=scoped_branch.pk)
        return Branch.objects.none()
    return _active_branches()


def _ensure_transfer_scope(request, transfer):
    if not request.user.is_storekeeper:
        return
    assignment = _storekeeper_assignment(request)
    if not assignment or not assignment.location_id or not assignment.location.branch_id:
        raise PermissionDenied
    branch_id = assignment.location.branch_id
    if (
        transfer.from_location.branch_id != branch_id
        and transfer.to_location.branch_id != branch_id
    ):
        raise PermissionDenied


def _storekeeper_branch_id(request):
    assignment = _storekeeper_assignment(request)
    if not assignment or not assignment.location_id or not assignment.location.branch_id:
        return None
    return assignment.location.branch_id


def _ensure_location_scope(request, location):
    if not request.user.is_storekeeper:
        return
    branch_id = _storekeeper_branch_id(request)
    if not branch_id:
        raise PermissionDenied
    if not location or location.branch_id != branch_id:
        raise PermissionDenied


def _ensure_asset_scope(request, asset):
    if not request.user.is_storekeeper:
        return
    if not asset.location_id:
        raise PermissionDenied
    _ensure_location_scope(request, asset.location)



@capability_required(Capability.INVENTORY_READ)
def product_list(request):
    products = Product.objects.filter(is_active=True).select_related('category')

    # Search
    query = request.GET.get('q')
    if query:
        products = products.filter(
            Q(name__icontains=query) |
            Q(sku__icontains=query)
        )

    # Filter by category
    category_id = request.GET.get('category')
    if category_id:
        products = products.filter(category_id=category_id)

    # Filter low stock only
    low_stock_only = request.GET.get('low_stock')
    if low_stock_only:
        products = products.filter(stock_quantity__lte=F('low_stock_alert'))

    low_stock = products.filter(stock_quantity__lte=F('low_stock_alert'))

    # Pagination
    paginator = Paginator(products, 20)
    page = request.GET.get('page')
    products = paginator.get_page(page)

    categories = Category.objects.all()

    return render(request, 'inventory/product_list.html', {
        'products': products,
        'low_stock_count': low_stock.count(),
        'categories': categories,
        'query': query or '',
        'current_category': category_id or '',
    })

@capability_required(Capability.INVENTORY_READ)
def category_list(request):
    categories = Category.objects.all()
    return render(request, 'inventory/category_list.html', {'categories': categories})


@capability_required(Capability.INVENTORY_ITEM_CREATE)
def category_create(request):
    if request.method == 'POST':
        form = CategoryForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, 'Category created.')
            return redirect('inventory:category_list')
    else:
        form = CategoryForm()
    return render(request, 'inventory/category_form.html', {'form': form})

@capability_required(Capability.INVENTORY_ITEM_CREATE)
def product_create(request):
    if request.method == 'POST':
        form = ProductForm(request.POST, request.FILES, user=request.user)
        if form.is_valid():
            product = form.save()
            # log opening stock as stock_in
            if product.stock_quantity > 0:
                StockMovement.objects.create(
                    product=product,
                    movement_type=StockMovement.MovementType.STOCK_IN,
                    quantity=product.stock_quantity,
                    cost_price=product.cost_price,
                    note='Opening stock',
                    created_by=request.user,
                )
            messages.success(request, f'{product.name} added.')
            return redirect('inventory:product_list')
    else:
        form = ProductForm(user=request.user)
    return render(request, 'inventory/product_form.html', {'form': form})


@capability_required(Capability.INVENTORY_READ)
def product_detail(request, pk):
    product = get_object_or_404(Product, pk=pk)
    _, branch, _, missing_assignment = _resolve_branch_scope(request)
    movements = product.movements.all()

    if missing_assignment:
        _notify_missing_store_assignment(request)
        movements = movements.none()
    elif branch:
        movements = movements.filter(location__branch=branch)

    movements = movements.order_by('-date', '-created_at')[:30]
    return render(request, 'inventory/product_detail.html', {
        'product': product,
        'movements': movements,
        'scoped_branch': branch,
    })


@capability_required(Capability.INVENTORY_ITEM_UPDATE)
def product_edit(request, pk):
    product = get_object_or_404(Product, pk=pk)
    if request.method == 'POST':
        form = ProductForm(request.POST, request.FILES, instance=product, user=request.user)
        if form.is_valid():
            form.save()
            messages.success(request, 'Product updated.')
            return redirect('inventory:product_detail', pk=pk)
    else:
        form = ProductForm(instance=product, user=request.user)
    return render(request, 'inventory/product_form.html', {'form': form, 'product': product})


@capability_required(Capability.INVENTORY_STOCK_ADJUST)
def stock_adjust(request, pk):
    product = get_object_or_404(Product, pk=pk)
    if request.method == 'POST':
        form = StockAdjustmentForm(request.POST)
        if form.is_valid():
            qty = form.cleaned_data['quantity']
            mtype = form.cleaned_data['movement_type']
            note = form.cleaned_data['note']
            if mtype == 'stock_in':
                add_stock(product, qty, note=note, user=request.user)
            else:
                try:
                    deduct_stock(product, qty, note=note, user=request.user)
                except ValueError as e:
                    messages.error(request, str(e))
                    return redirect('inventory:stock_adjust', pk=pk)
            messages.success(request, 'Stock updated.')
            return redirect('inventory:product_detail', pk=pk)
    else:
        form = StockAdjustmentForm()
    return render(request, 'inventory/stock_adjust.html', {'form': form, 'product': product})


# ── Suppliers ──────────────────────────────────────────────────────────────

@capability_required(Capability.INVENTORY_READ)
def supplier_list(request):
    query = request.GET.get('q')
    suppliers = Supplier.objects.filter(is_active=True)
    if query:
        suppliers = suppliers.filter(name__icontains=query)
    return render(request, 'inventory/supplier_list.html', {
        'suppliers': suppliers,
        'query': query or '',
    })


@capability_required(Capability.INVENTORY_SUPPLIER_MANAGE)
def supplier_create(request):
    if request.method == 'POST':
        form = SupplierForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, 'Supplier added.')
            return redirect('inventory:supplier_list')
    else:
        form = SupplierForm()
    return render(request, 'inventory/supplier_form.html', {
        'form': form,
        'title': 'Add Supplier',
    })


@capability_required(Capability.INVENTORY_SUPPLIER_MANAGE)
def supplier_edit(request, pk):
    supplier = get_object_or_404(Supplier, pk=pk)
    if request.method == 'POST':
        form = SupplierForm(request.POST, instance=supplier)
        if form.is_valid():
            form.save()
            messages.success(request, 'Supplier updated.')
            return redirect('inventory:supplier_list')
    else:
        form = SupplierForm(instance=supplier)
    return render(request, 'inventory/supplier_form.html', {
        'form': form,
        'title': f'Edit {supplier.name}',
    })


@capability_required(Capability.INVENTORY_READ)
def supplier_detail(request, pk):
    supplier = get_object_or_404(Supplier, pk=pk)
    purchase_orders = supplier.purchase_orders.order_by('-date')
    return render(request, 'inventory/supplier_detail.html', {
        'supplier': supplier,
        'purchase_orders': purchase_orders,
    })


# ── Purchase Orders ────────────────────────────────────────────────────────

def generate_po_reference():
    last = PurchaseOrder.objects.order_by('-id').first()
    next_id = (last.id + 1) if last else 1
    return f'PO-{next_id:04d}'


@capability_required(Capability.INVENTORY_READ)
def po_list(request):
    orders = PurchaseOrder.objects.select_related('supplier').order_by('-date', '-created_at')

    status = request.GET.get('status')
    if status:
        orders = orders.filter(status=status)

    query = request.GET.get('q')
    if query:
        orders = orders.filter(
            Q(reference__icontains=query) |
            Q(supplier__name__icontains=query)
        )

    from django.core.paginator import Paginator
    paginator = Paginator(orders, 20)
    page = request.GET.get('page')
    orders = paginator.get_page(page)

    return render(request, 'inventory/po_list.html', {
        'orders': orders,
        'current_status': status or '',
        'query': query or '',
    })


@capability_required(Capability.INVENTORY_PURCHASE_MANAGE)
def po_create(request):
    if request.method == 'POST':
        form = PurchaseOrderForm(request.POST)
        formset = PurchaseOrderItemFormSet(request.POST)
        if form.is_valid() and formset.is_valid():
            po = form.save(commit=False)
            po.reference = generate_po_reference()
            po.created_by = request.user
            po.save()

            items = formset.save(commit=False)
            for item in items:
                item.purchase_order = po
                item.save()

            for obj in formset.deleted_objects:
                obj.delete()

            messages.success(request, f'{po.reference} created.')
            return redirect('inventory:po_detail', pk=po.pk)
    else:
        form = PurchaseOrderForm(initial={'date': timezone.now().date()})
        formset = PurchaseOrderItemFormSet()

    return render(request, 'inventory/po_form.html', {
        'form': form,
        'formset': formset,
    })


@capability_required(Capability.INVENTORY_READ)
def po_detail(request, pk):
    po = get_object_or_404(PurchaseOrder, pk=pk)
    return render(request, 'inventory/po_detail.html', {'po': po})


@capability_required(Capability.INVENTORY_PURCHASE_RECEIVE)
def po_receive(request, pk):
    """
    Mark PO as received:
    - Add stock for each item
    - Deduct from account if payment account selected
    - Log stock movements & account transaction
    """
    po = get_object_or_404(PurchaseOrder, pk=pk)

    if po.status != PurchaseOrder.Status.PENDING:
        messages.warning(request, 'This order has already been processed.')
        return redirect('inventory:po_detail', pk=pk)

    if request.method == 'POST':
        from django.db import transaction
        with transaction.atomic():
            # Add stock for each item
            for item in po.items.all():
                add_stock(
                    product=item.product,
                    quantity=item.quantity,
                    cost_price=item.unit_cost,
                    reference=po.reference,
                    note=f'Purchase Order {po.reference}',
                    user=request.user,
                )

            # Deduct from account if provided
            if po.account:
                if po.account.balance < po.total:
                    messages.error(
                        request,
                        f'Insufficient balance in {po.account.name}. '
                        f'Available: {po.account.balance}'
                    )
                    return redirect('inventory:po_detail', pk=pk)

                po.account.withdraw(po.total)
                po.amount_paid = po.total
                AccountTransaction.objects.create(
                    account=po.account,
                    tx_type=AccountTransaction.TxType.ADJUSTMENT,
                    direction='out',
                    amount=po.total,
                    description=f'Stock purchase {po.reference}',
                    reference=po.reference,
                    date=po.date,
                )

            # Mark as received
            po.status = PurchaseOrder.Status.RECEIVED
            po.received_date = timezone.now().date()
            po.save()

        messages.success(request, f'{po.reference} marked as received. Stock updated.')
        return redirect('inventory:po_detail', pk=pk)

    return render(request, 'inventory/po_confirm_receive.html', {'po': po})


@capability_required(Capability.INVENTORY_STOCK_APPROVE)
def po_cancel(request, pk):
    po = get_object_or_404(PurchaseOrder, pk=pk)
    if po.status != PurchaseOrder.Status.PENDING:
        messages.warning(request, 'Only pending orders can be cancelled.')
        return redirect('inventory:po_detail', pk=pk)
    po.status = PurchaseOrder.Status.CANCELLED
    po.save()
    messages.success(request, f'{po.reference} cancelled.')
    return redirect('inventory:po_list')


def generate_purchase_return_reference():
    last = PurchaseReturn.objects.order_by('-id').first()
    next_id = (last.id + 1) if last else 1
    return f'PR-{next_id:04d}'


@capability_required(Capability.INVENTORY_READ)
def purchase_return_list(request):
    returns = PurchaseReturn.objects.select_related('purchase_order', 'created_by').order_by('-date')
    paginator = Paginator(returns, 20)
    page = request.GET.get('page')
    returns = paginator.get_page(page)
    return render(request, 'inventory/purchase_return_list.html', {'returns': returns})


@capability_required(Capability.INVENTORY_PURCHASE_MANAGE)
def purchase_return_create(request):
    if request.method == 'POST':
        form = PurchaseReturnForm(request.POST)
        formset = PurchaseReturnItemFormSet(request.POST)
        if form.is_valid() and formset.is_valid():
            purchase_return = form.save(commit=False)
            purchase_return.reference = generate_purchase_return_reference()
            purchase_return.created_by = request.user
            purchase_return.save()
            items = formset.save(commit=False)
            for item in items:
                item.purchase_return = purchase_return
                item.save()
            for obj in formset.deleted_objects:
                obj.delete()
            messages.success(request, f'{purchase_return.reference} created.')
            return redirect('inventory:purchase_return_detail', pk=purchase_return.pk)
    else:
        form = PurchaseReturnForm(initial={'date': timezone.now().date()})
        formset = PurchaseReturnItemFormSet()
    return render(
        request,
        'inventory/purchase_return_form.html',
        {'form': form, 'formset': formset},
    )


@capability_required(Capability.INVENTORY_READ)
def purchase_return_detail(request, pk):
    purchase_return = get_object_or_404(PurchaseReturn, pk=pk)
    return render(request, 'inventory/purchase_return_detail.html', {'purchase_return': purchase_return})


@capability_required(Capability.INVENTORY_PURCHASE_RECEIVE)
def purchase_return_process(request, pk):
    purchase_return = get_object_or_404(PurchaseReturn, pk=pk)
    if purchase_return.status != PurchaseReturn.Status.DRAFT:
        messages.warning(request, 'Only draft returns can be processed.')
        return redirect('inventory:purchase_return_detail', pk=pk)

    if request.method == 'POST':
        with transaction.atomic():
            for item in purchase_return.items.select_related('product'):
                deduct_stock(
                    product=item.product,
                    quantity=item.quantity,
                    reference=purchase_return.reference,
                    note=f'Purchase return {purchase_return.reference}',
                    user=request.user,
                    movement_type=StockMovement.MovementType.RETURN,
                )

            po = purchase_return.purchase_order
            if po.account:
                po.account.deposit(purchase_return.total)
                AccountTransaction.objects.create(
                    account=po.account,
                    tx_type=AccountTransaction.TxType.ADJUSTMENT,
                    direction='in',
                    amount=purchase_return.total,
                    description=f'Purchase return {purchase_return.reference}',
                    reference=purchase_return.reference,
                    date=purchase_return.date,
                )

            purchase_return.status = PurchaseReturn.Status.PROCESSED
            purchase_return.processed_by = request.user
            purchase_return.processed_date = timezone.now().date()
            purchase_return.save()

        messages.success(request, f'{purchase_return.reference} processed.')
        return redirect('inventory:purchase_return_detail', pk=pk)

    return render(
        request,
        'inventory/purchase_return_process_confirm.html',
        {'purchase_return': purchase_return},
    )


@capability_required(Capability.INVENTORY_EXPORT)
def export_products_csv(request):
    products = Product.objects.filter(
        is_active=True
    ).select_related('category').order_by('category__name', 'name')

    headers = [
        'Name', 'SKU', 'Category', 'Unit',
        'Cost Price', 'Selling Price',
        'Stock Quantity', 'Stock Value',
        'Low Stock Alert', 'Overstock Alert', 'Status'
    ]

    rows = []
    for p in products:
        rows.append([
            p.name,
            p.sku or '',
            p.category.name if p.category else '',
            p.get_unit_display(),
            p.cost_price,
            p.selling_price,
            p.stock_quantity,
            p.stock_value,
            p.low_stock_alert,
            p.overstock_alert,
            'Low Stock' if p.is_low_stock else ('Overstock' if p.is_overstock else 'OK'),
        ])

    return export_csv('products', headers, rows)


def generate_transfer_reference():
    last = StockTransfer.objects.order_by('-id').first()
    next_id = (last.id + 1) if last else 1
    return f'TRN-{next_id:04d}'


@capability_required(Capability.INVENTORY_READ)
def branch_list(request):
    _, branch, branch_locked, missing_assignment = _resolve_branch_scope(request)
    branches = Branch.objects.order_by('name')
    if branch_locked:
        if missing_assignment:
            _notify_missing_store_assignment(request)
            branches = branches.none()
        elif branch:
            branches = branches.filter(pk=branch.pk)
    return render(request, 'inventory/branch_list.html', {'branches': branches})


@capability_required(Capability.INVENTORY_STOCK_APPROVE)
def branch_create(request):
    if request.method == 'POST':
        form = BranchForm(request.POST)
        if form.is_valid():
            form.save()
            messages.success(request, 'Branch created.')
            return redirect('inventory:branch_list')
    else:
        form = BranchForm()
    return render(
        request,
        'inventory/branch_form.html',
        {'form': form, 'title': 'Create Branch'},
    )


@capability_required(Capability.INVENTORY_STOCK_APPROVE)
def branch_edit(request, pk):
    branch = get_object_or_404(Branch, pk=pk)
    if request.method == 'POST':
        form = BranchForm(request.POST, instance=branch)
        if form.is_valid():
            form.save()
            messages.success(request, 'Branch updated.')
            return redirect('inventory:branch_list')
    else:
        form = BranchForm(instance=branch)
    return render(
        request,
        'inventory/branch_form.html',
        {'form': form, 'title': f'Edit Branch: {branch.name}'},
    )


@capability_required(Capability.INVENTORY_READ)
def location_list(request):
    branch_value, branch, branch_locked, missing_assignment = _resolve_branch_scope(request)
    locations = StockLocation.objects.select_related('branch').order_by('name')
    if missing_assignment:
        _notify_missing_store_assignment(request)
        locations = locations.none()
    elif branch:
        locations = locations.filter(branch=branch)
    return render(
        request,
        'inventory/location_list.html',
        {
            'locations': locations,
            'branches': _branch_options_for_request(request, branch),
            'current_branch': branch_value,
            'branch_locked': branch_locked,
        },
    )


@capability_required(Capability.INVENTORY_ITEM_CREATE)
def location_create(request):
    _, scoped_branch, branch_locked, missing_assignment = _resolve_branch_scope(request)
    form_branch_scope = scoped_branch if branch_locked else None
    if missing_assignment:
        _notify_missing_store_assignment(request)
        return redirect('inventory:location_list')

    if request.method == 'POST':
        form = StockLocationForm(
            request.POST,
            user=request.user,
            branch_scope=form_branch_scope,
        )
        if form.is_valid():
            form.save()
            messages.success(request, 'Location created.')
            return redirect('inventory:location_list')
    else:
        form = StockLocationForm(user=request.user, branch_scope=form_branch_scope)
    return render(
        request,
        'inventory/location_form.html',
        {'form': form, 'branch_locked': branch_locked},
    )


@capability_required(Capability.INVENTORY_READ)
def transfer_list(request):
    branch_value, branch, branch_locked, missing_assignment = _resolve_branch_scope(request)
    transfers = StockTransfer.objects.select_related(
        'from_location', 'from_location__branch', 'to_location', 'to_location__branch',
        'requested_by', 'approved_by'
    ).order_by('-requested_at')

    if missing_assignment:
        _notify_missing_store_assignment(request)
        transfers = transfers.none()
    elif branch:
        transfers = transfers.filter(
            Q(from_location__branch=branch) | Q(to_location__branch=branch)
        )

    status = request.GET.get('status')
    if status:
        transfers = transfers.filter(status=status)
    paginator = Paginator(transfers, 20)
    page = request.GET.get('page')
    transfers = paginator.get_page(page)
    return render(
        request,
        'inventory/transfer_list.html',
        {
            'transfers': transfers,
            'current_status': status or '',
            'branches': _branch_options_for_request(request, branch),
            'current_branch': branch_value,
            'branch_locked': branch_locked,
        },
    )


@capability_required(Capability.INVENTORY_TRANSFER_REQUEST)
def transfer_create(request):
    _, scoped_branch, branch_locked, missing_assignment = _resolve_branch_scope(request)
    form_branch_scope = scoped_branch if branch_locked else None
    if missing_assignment:
        _notify_missing_store_assignment(request)
        return redirect('inventory:transfer_list')

    if request.method == 'POST':
        form = StockTransferForm(
            request.POST,
            user=request.user,
            branch_scope=form_branch_scope,
        )
        formset = StockTransferItemFormSet(request.POST)
        if form.is_valid() and formset.is_valid():
            transfer = form.save(commit=False)
            transfer.reference = generate_transfer_reference()
            transfer.requested_by = request.user
            transfer.save()
            items = formset.save(commit=False)
            for item in items:
                item.transfer = transfer
                item.save()
            for obj in formset.deleted_objects:
                obj.delete()
            messages.success(request, f'{transfer.reference} transfer request created.')
            return redirect('inventory:transfer_detail', pk=transfer.pk)
    else:
        form = StockTransferForm(user=request.user, branch_scope=form_branch_scope)
        formset = StockTransferItemFormSet()
    return render(
        request,
        'inventory/transfer_form.html',
        {'form': form, 'formset': formset, 'branch_locked': branch_locked},
    )


@capability_required(Capability.INVENTORY_READ)
def transfer_detail(request, pk):
    transfer = get_object_or_404(
        StockTransfer.objects.select_related('from_location', 'to_location', 'requested_by', 'approved_by'),
        pk=pk,
    )
    _ensure_transfer_scope(request, transfer)
    return render(request, 'inventory/transfer_detail.html', {'transfer': transfer})


@capability_required(Capability.INVENTORY_TRANSFER_APPROVE)
def transfer_approve(request, pk):
    transfer = get_object_or_404(StockTransfer, pk=pk)
    _ensure_transfer_scope(request, transfer)
    if transfer.status != StockTransfer.Status.REQUESTED:
        messages.warning(request, 'Only requested transfers can be approved.')
        return redirect('inventory:transfer_detail', pk=pk)
    transfer.status = StockTransfer.Status.APPROVED
    transfer.approved_by = request.user
    transfer.approved_at = timezone.now()
    transfer.save()
    messages.success(request, f'{transfer.reference} approved.')
    return redirect('inventory:transfer_detail', pk=pk)


@capability_required(Capability.INVENTORY_TRANSFER_APPROVE)
def transfer_complete(request, pk):
    transfer = get_object_or_404(StockTransfer, pk=pk)
    _ensure_transfer_scope(request, transfer)
    if transfer.status != StockTransfer.Status.APPROVED:
        messages.warning(request, 'Only approved transfers can be completed.')
        return redirect('inventory:transfer_detail', pk=pk)

    if request.method == 'POST':
        with transaction.atomic():
            for item in transfer.items.select_related('product', 'batch', 'serial'):
                deduct_stock(
                    product=item.product,
                    quantity=item.quantity,
                    location=transfer.from_location,
                    batch=item.batch,
                    serial_numbers=[item.serial.serial_number] if item.serial else None,
                    movement_type=StockMovement.MovementType.TRANSFER_OUT,
                    reference=transfer.reference,
                    note=f'Transfer to {transfer.to_location.name}',
                    user=request.user,
                )
                add_stock(
                    product=item.product,
                    quantity=item.quantity,
                    location=transfer.to_location,
                    batch_number=item.batch.batch_number if item.batch else '',
                    expiry_date=item.batch.expiry_date if item.batch else None,
                    serial_numbers=[item.serial.serial_number] if item.serial else None,
                    movement_type=StockMovement.MovementType.TRANSFER_IN,
                    reference=transfer.reference,
                    note=f'Transfer from {transfer.from_location.name}',
                    user=request.user,
                )
            transfer.status = StockTransfer.Status.COMPLETED
            transfer.completed_at = timezone.now()
            transfer.save()
        messages.success(request, f'{transfer.reference} completed.')
        return redirect('inventory:transfer_detail', pk=pk)

    return render(request, 'inventory/transfer_complete_confirm.html', {'transfer': transfer})


@capability_required(Capability.INVENTORY_READ)
def adjustment_request_list(request):
    branch_value, branch, branch_locked, missing_assignment = _resolve_branch_scope(request)
    requests = StockAdjustmentRequest.objects.select_related(
        'product', 'location', 'location__branch', 'requested_by', 'approved_by'
    ).order_by('-created_at')

    if missing_assignment:
        _notify_missing_store_assignment(request)
        requests = requests.none()
    elif branch:
        requests = requests.filter(location__branch=branch)

    status = request.GET.get('status')
    if status:
        requests = requests.filter(status=status)
    paginator = Paginator(requests, 20)
    page = request.GET.get('page')
    requests = paginator.get_page(page)
    return render(
        request,
        'inventory/adjustment_request_list.html',
        {
            'requests': requests,
            'current_status': status or '',
            'branches': _branch_options_for_request(request, branch),
            'current_branch': branch_value,
            'branch_locked': branch_locked,
        },
    )


@capability_required(Capability.INVENTORY_STOCK_ADJUST)
def adjustment_request_create(request):
    _, scoped_branch, branch_locked, missing_assignment = _resolve_branch_scope(request)
    form_branch_scope = scoped_branch if branch_locked else None
    if missing_assignment:
        _notify_missing_store_assignment(request)
        return redirect('inventory:adjustment_request_list')

    if request.method == 'POST':
        form = StockAdjustmentRequestForm(
            request.POST,
            user=request.user,
            branch_scope=form_branch_scope,
        )
        if form.is_valid():
            adjustment = form.save(commit=False)
            adjustment.requested_by = request.user
            adjustment.save()
            messages.success(request, 'Stock adjustment request submitted for approval.')
            return redirect('inventory:adjustment_request_list')
    else:
        form = StockAdjustmentRequestForm(
            user=request.user,
            branch_scope=form_branch_scope,
        )
    return render(
        request,
        'inventory/adjustment_request_form.html',
        {'form': form, 'branch_locked': branch_locked},
    )


@capability_required(Capability.INVENTORY_STOCK_APPROVE)
def adjustment_request_approve(request, pk):
    adjustment = get_object_or_404(StockAdjustmentRequest, pk=pk)
    _ensure_location_scope(request, adjustment.location)
    if adjustment.status != StockAdjustmentRequest.Status.PENDING:
        messages.warning(request, 'This request has already been processed.')
        return redirect('inventory:adjustment_request_list')

    if request.method == 'POST':
        action = request.POST.get('action')
        if action == 'reject':
            adjustment.status = StockAdjustmentRequest.Status.REJECTED
            adjustment.approved_by = request.user
            adjustment.approved_at = timezone.now()
            adjustment.save()
            messages.success(request, 'Adjustment request rejected.')
            return redirect('inventory:adjustment_request_list')

        movement_map = {
            StockAdjustmentRequest.Reason.DAMAGED: StockMovement.MovementType.DAMAGED,
            StockAdjustmentRequest.Reason.LOST: StockMovement.MovementType.LOST,
            StockAdjustmentRequest.Reason.EXPIRED: StockMovement.MovementType.EXPIRED,
            StockAdjustmentRequest.Reason.CORRECTION: StockMovement.MovementType.ADJUSTMENT,
        }
        movement_type = movement_map.get(adjustment.reason, StockMovement.MovementType.ADJUSTMENT)

        if adjustment.reason == StockAdjustmentRequest.Reason.CORRECTION and adjustment.quantity > 0:
            add_stock(
                product=adjustment.product,
                quantity=adjustment.quantity,
                location=adjustment.location,
                movement_type=movement_type,
                reference=f'ADJ-{adjustment.pk}',
                note=adjustment.note or 'Approved correction adjustment',
                user=request.user,
            )
        else:
            deduct_stock(
                product=adjustment.product,
                quantity=adjustment.quantity,
                location=adjustment.location,
                movement_type=movement_type,
                reference=f'ADJ-{adjustment.pk}',
                note=adjustment.note or 'Approved stock adjustment',
                user=request.user,
            )

        adjustment.status = StockAdjustmentRequest.Status.APPROVED
        adjustment.approved_by = request.user
        adjustment.approved_at = timezone.now()
        adjustment.save()
        messages.success(request, 'Adjustment request approved and posted to stock.')
        return redirect('inventory:adjustment_request_list')

    return render(
        request,
        'inventory/adjustment_request_approve.html',
        {'adjustment': adjustment},
    )


@capability_required(Capability.REPORTS_VIEW)
def low_stock_report(request):
    branch_value, branch, branch_locked, missing_assignment = _resolve_branch_scope(request)
    products = Product.objects.filter(is_active=True).select_related('category')

    if missing_assignment:
        _notify_missing_store_assignment(request)
        products = products.none()
    elif branch:
        products = products.filter(
            location_stocks__location__branch=branch
        ).annotate(
            branch_stock=Coalesce(
                Sum(
                    'location_stocks__quantity',
                ),
                Value(0),
                output_field=DecimalField(max_digits=15, decimal_places=2),
            )
        ).filter(branch_stock__lte=F('low_stock_alert')).order_by('branch_stock', 'name')
    else:
        products = products.filter(
            stock_quantity__lte=F('low_stock_alert')
        ).order_by('stock_quantity', 'name')

    return render(
        request,
        'inventory/low_stock_report.html',
        {
            'products': products,
            'branches': _branch_options_for_request(request, branch),
            'current_branch': branch_value,
            'branch_locked': branch_locked,
        },
    )


@capability_required(Capability.REPORTS_VIEW)
def expired_stock_report(request):
    branch_value, branch, branch_locked, missing_assignment = _resolve_branch_scope(request)
    batches = ProductBatch.objects.filter(
        is_active=True, quantity__gt=0, expiry_date__lt=timezone.now().date()
    ).select_related('product', 'location', 'location__branch').order_by('expiry_date')
    if missing_assignment:
        _notify_missing_store_assignment(request)
        batches = batches.none()
    elif branch:
        batches = batches.filter(location__branch=branch)
    return render(
        request,
        'inventory/expired_stock_report.html',
        {
            'batches': batches,
            'branches': _branch_options_for_request(request, branch),
            'current_branch': branch_value,
            'branch_locked': branch_locked,
        },
    )


@capability_required(Capability.REPORTS_VIEW)
def stock_movement_report(request):
    branch_value, branch, branch_locked, missing_assignment = _resolve_branch_scope(request)
    movements = StockMovement.objects.select_related(
        'product', 'location', 'location__branch', 'batch', 'serial', 'created_by'
    ).order_by('-date', '-created_at')
    if missing_assignment:
        _notify_missing_store_assignment(request)
        movements = movements.none()
    elif branch:
        movements = movements.filter(location__branch=branch)
    query = request.GET.get('q')
    if query:
        movements = movements.filter(
            Q(product__name__icontains=query)
            | Q(reference__icontains=query)
            | Q(note__icontains=query)
        )
    paginator = Paginator(movements, 50)
    page = request.GET.get('page')
    movements = paginator.get_page(page)
    return render(
        request,
        'inventory/stock_movement_report.html',
        {
            'movements': movements,
            'query': query or '',
            'branches': _branch_options_for_request(request, branch),
            'current_branch': branch_value,
            'branch_locked': branch_locked,
        },
    )


@capability_required(Capability.ASSET_READ)
def asset_list(request):
    branch_value, branch, branch_locked, missing_assignment = _resolve_branch_scope(request)
    assets = Asset.objects.select_related('location', 'location__branch').order_by('name')

    if missing_assignment:
        _notify_missing_store_assignment(request)
        assets = assets.none()
    elif branch:
        assets = assets.filter(location__branch=branch)

    return render(
        request,
        'inventory/asset_list.html',
        {
            'assets': assets,
            'branches': _branch_options_for_request(request, branch),
            'current_branch': branch_value,
            'branch_locked': branch_locked,
        },
    )


@capability_required(Capability.ASSET_MANAGE)
def asset_create(request):
    _, scoped_branch, branch_locked, missing_assignment = _resolve_branch_scope(request)
    form_branch_scope = scoped_branch if branch_locked else None
    if missing_assignment:
        _notify_missing_store_assignment(request)
        return redirect('inventory:asset_list')

    if request.method == 'POST':
        form = AssetForm(
            request.POST,
            user=request.user,
            branch_scope=form_branch_scope,
        )
        if form.is_valid():
            asset = form.save()
            messages.success(request, f'Asset {asset.name} created.')
            return redirect('inventory:asset_detail', pk=asset.pk)
    else:
        form = AssetForm(user=request.user, branch_scope=form_branch_scope)
    return render(
        request,
        'inventory/asset_form.html',
        {'form': form, 'branch_locked': branch_locked},
    )


@capability_required(Capability.ASSET_READ)
def asset_detail(request, pk):
    asset = get_object_or_404(
        Asset.objects.select_related('location', 'location__branch').prefetch_related(
            'assignments', 'maintenance_logs'
        ),
        pk=pk,
    )
    _ensure_asset_scope(request, asset)
    assign_form = AssetAssignmentForm(initial={'asset': asset.pk})
    maintenance_form = AssetMaintenanceForm(initial={'asset': asset.pk})
    return render(
        request,
        'inventory/asset_detail.html',
        {'asset': asset, 'assign_form': assign_form, 'maintenance_form': maintenance_form},
    )


@capability_required(Capability.ASSET_MANAGE)
def asset_assign(request, pk):
    asset = get_object_or_404(Asset.objects.select_related('location', 'location__branch'), pk=pk)
    _ensure_asset_scope(request, asset)
    if request.method == 'POST':
        form = AssetAssignmentForm(request.POST)
        if form.is_valid():
            assignment = form.save(commit=False)
            assignment.asset = asset
            assignment.save()
            messages.success(request, 'Asset assignment recorded.')
    return redirect('inventory:asset_detail', pk=pk)


@capability_required(Capability.ASSET_MANAGE)
def asset_maintenance(request, pk):
    asset = get_object_or_404(Asset.objects.select_related('location', 'location__branch'), pk=pk)
    _ensure_asset_scope(request, asset)
    if request.method == 'POST':
        form = AssetMaintenanceForm(request.POST)
        if form.is_valid():
            maintenance = form.save(commit=False)
            maintenance.asset = asset
            maintenance.save()
            messages.success(request, 'Asset maintenance log recorded.')
    return redirect('inventory:asset_detail', pk=pk)
