from django.utils.timezone import now
from datetime import date, datetime, time
from finance.models import AddExpense, ExpenseCategory
from humanresource.models import Employment
from django.shortcuts import get_object_or_404, render, redirect
from .models import Order, OrderItem, ReceiveOrder, ReceivedOrderItem
from .models import Order, ReceiveOrder, OrderItem, ReceivedOrderItem
from .models import Doctor, DoctorCommission
from django.shortcuts import get_object_or_404, render
from .models import ConsumerAccount, Consumer, Supplier
from .forms import (
    SaleOrderForm,
    BalanceOpeningForm,
    OrderForm,
    PaymentFromConsumerForm,
    PaymentToSupplierForm,
    DoctorProductCommissionForm,
    ConsumerProductDiscountForm,
)
from .models import (
    SaleOrder,
    SaleOrderItems,
    Product,
    ShareHolders,
    Doctor,
    ReceiveOrder,
    ReceivedOrderItem,
    ProductBatches,
    DoctorCommission,
    Consumer,
    ConsumerAccount,
    Payment_From_Consumer,
    Payment_To_Supplier,
    SupplierAccount,
    Order,
    OrderItem,
    ReceiveOrder,
    ReceivedOrderItem,
    DoctorProductCommission,
    ConsumerProductDiscount,
)
from django.shortcuts import get_object_or_404, redirect, render
from inventory.models import ProductBatches
from django.views.decorators.http import require_GET
from django.shortcuts import render, get_object_or_404, redirect
from django.core.exceptions import PermissionDenied
from inventory.forms import SaleOrderForm
from inventory.models import SaleOrder, SaleOrderItems, ProductBatches
from django.http import JsonResponse
from django.contrib.auth.decorators import permission_required
from django.db.models import Sum
from .models import Consumer, ConsumerAccount
from django.db.models import Sum, DecimalField, ExpressionWrapper, F
from finance.models import ShareHolderDeposit
from django.shortcuts import render, get_object_or_404
from .models import SaleOrder, SaleOrderItems, Product, ShareHolders, Doctor, ReceiveOrder, ReceivedOrderItem, ProductBatches, DoctorCommission, Consumer, ConsumerAccount, Payment_From_Consumer, Order, OrderItem, ReceiveOrder, ReceivedOrderItem
from home.models import CompanyProfile
from django.http import HttpResponse
from datetime import date, datetime
from django.contrib import messages
from django.shortcuts import render
from django.utils import formats
from collections import defaultdict
from decimal import Decimal
from dateutil.parser import parse
from django.contrib.auth.decorators import login_required
from django.db.models import Q
from django.core.paginator import Paginator
from django.core.exceptions import FieldDoesNotExist
from django.urls import reverse
from django.shortcuts import (
    render, redirect, get_object_or_404, get_list_or_404
)
from django.utils.html import escape
from django.db.models import (
    Q, Count, ProtectedError
)
from django.contrib.auth.decorators import login_required, permission_required
from django.contrib import messages
from django.http import JsonResponse
from django.contrib.admin.models import LogEntry
from django.contrib.sessions.models import Session
from django.views.decorators.http import require_GET
from .models import (
    SaleOrder,
    SaleOrderItems,
    Product,
    ShareHolders,
    Doctor,
    ReceiveOrder,
    ReceivedOrderItem,
    ProductBatches,
    DoctorCommission,
    Consumer,
    ConsumerAccount,
    Payment_From_Consumer,
    Order,
    OrderItem,
    ReceiveOrder,
    ReceivedOrderItem,
    ConsumerReturn,
    ConsumerReturnItem,
    SupplierReturn,
    SupplierReturnItem,
)

from django.contrib.admin.models import LogEntry, ADDITION, CHANGE, DELETION
from django.contrib.sessions.models import Session
from django.utils import timezone
from django.contrib.auth.decorators import login_required
from django.db.models import Count
from django.shortcuts import render
from django.contrib.auth import get_user_model

User = get_user_model()
# Utility function to safely parse date strings
def parse_date_safe(value):
    try:
        return date.fromisoformat(value)
    except (TypeError, ValueError):
        return None

@login_required
def admin_page(request):
    # Recent 20 logs of the current user
    user_logs = (
        LogEntry.objects
        .select_related('content_type', 'user')
        .filter(user=request.user)
        .order_by('-action_time')[:20]
    )

    # Action summary (Add, Change, Delete)
    counts = (
        LogEntry.objects.filter(user=request.user)
        .values('action_flag')
        .annotate(count=Count('id'))
    )
    action_summary = {'add': 0, 'change': 0, 'delete': 0}
    for item in counts:
        flag = item['action_flag']
        if flag == ADDITION:
            action_summary['add'] = item['count']
        elif flag == CHANGE:
            action_summary['change'] = item['count']
        elif flag == DELETION:
            action_summary['delete'] = item['count']

    # Active sessions for current user
    active_sessions = []
    for session in Session.objects.filter(expire_date__gte=timezone.now()):
        data = session.get_decoded()
        uid = data.get('_auth_user_id')
        if str(request.user.id) == str(uid):
            active_sessions.append({
                'session_key': session.session_key,
                'expire_date': session.expire_date,
            })

    context = {
        'user_logs': user_logs,
        'action_summary': action_summary,
        'active_sessions': active_sessions,
        'last_login': request.user.last_login,
    }
    return render(request, "admin_pages/dashboard.html", context)

@require_GET
def product_search_api(request):
    q = request.GET.get('q', '')

    # Limit to products with matching titles, and preload related product info
    results = ProductBatches.objects.select_related('product') \
        .filter(product__title__icontains=q)[:10]

    data = []
    for batch in results:
        product = batch.product
        display_title = (
            f"{product.title} - Batch: {batch.batch_number} - QTY: {batch.quantity} - Price: {product.unit_price}"
        )
        data.append({
            'id': batch.id,
            'product_id': product.id,
            'title': display_title,
            'price': float(product.unit_price),
            'commission': float(product.commission),
        })

    return JsonResponse(data, safe=False)


@require_GET
def consumer_discount_map_api(request):
    """Return product discount mapping for a given consumer."""
    consumer_id = request.GET.get('consumer_id')
    if not consumer_id:
        return JsonResponse({})

    discounts = ConsumerProductDiscount.objects.filter(consumer_id=consumer_id)
    data = {
        d.product_id: {
            'discount': d.discount,
            'unit_price': float(d.unit_price) if d.unit_price is not None else None,
        }
        for d in discounts
    }
    return JsonResponse(data)


@require_GET
def doctor_commission_map_api(request):
    """Return product commission mapping for a given doctor."""
    doctor_id = request.GET.get("doctor_id")
    if not doctor_id:
        return JsonResponse({})

    commissions = DoctorProductCommission.objects.filter(doctor_id=doctor_id)
    data = {c.product_id: float(c.commission) for c in commissions}
    return JsonResponse(data)

def saleorder_list_view(request):
    """Display paginated list of SaleOrders."""
    if not request.user.has_perm('inventory.view_saleorder'):
        messages.error(request, "You do not have permission to access this page.")
        return redirect('inventory:admin_page')

    saleorders = SaleOrder.objects.all()

    query = request.GET.get('q', '')
    if query:
        saleorders = saleorders.filter(consumer__name__icontains=query)

    sort = request.GET.get('sort', 'newest')
    if sort == 'oldest':
        saleorders = saleorders.order_by('created_at')
    else:
        saleorders = saleorders.order_by('-created_at')

    paginator = Paginator(saleorders, 10)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)

    return render(request, 'admin_pages/saleorder_list.html', {
        'saleorders': page_obj,
        'search_query': query,
        'sort': sort,
    })

# views.py (simplified)
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required, permission_required
from django.core.exceptions import PermissionDenied
from django.contrib import messages
from .forms import SaleOrderForm
from .models import SaleOrder, SaleOrderItems, Doctor
import uuid
from django.db import IntegrityError
from decimal import Decimal

@login_required
@permission_required('inventory.add_saleorder', raise_exception=True)
def saleorder_create_or_update_view(request, pk=None):
    instance = get_object_or_404(SaleOrder, pk=pk) if pk else None
    form = SaleOrderForm(request.POST or None, instance=instance)
    saleorder_items = SaleOrderItems.objects.filter(saleorder=instance) if instance else []
    form_uuid = uuid.uuid4() if not instance else None

    if request.method == 'POST' and form.is_valid():
        total_forms = int(request.POST.get('form-TOTAL_FORMS', 0))
        items = []
        for i in range(total_forms):
            product_id = request.POST.get(f'form-{i}-product')
            quantity = Decimal(request.POST.get(f'form-{i}-quantity') or 0)
            if product_id and quantity:
                items.append({
                    'product_id': product_id,
                    'quantity': quantity,
                    'price': Decimal(request.POST.get(f'form-{i}-price') or 0),
                    'discount': int(request.POST.get(f'form-{i}-discount', 0)),
                    'commission': Decimal(request.POST.get(f'form-{i}-commission') or 0),
                    'net_price': Decimal(request.POST.get(f'form-{i}-net_price') or 0),
                    'doctor_id': request.POST.get(f'form-{i}-doctor') or None,
                })


        if not items:
            messages.error(request, "Sale order must contain at least one item.")
        else:
            saleorder = form.save(commit=False)
            if not instance:
                saleorder.transaction_id = request.POST.get('form_uuid') or uuid.uuid4()
                if SaleOrder.objects.filter(transaction_id=saleorder.transaction_id).exists():
                    messages.warning(request, "This sale order was already submitted.")
                    return redirect('inventory:saleorder_list')
                saleorder.created_by = request.user
            saleorder.updated_by = request.user
            try:
                saleorder.save()
            except IntegrityError:
                messages.warning(request, "Duplicate sale order detected.")
                return redirect('inventory:saleorder_list')

            if instance:
                old_items = SaleOrderItems.objects.filter(saleorder=saleorder)
                for old_item in old_items:
                    old_item.product.product.inventory += old_item.quantity
                    old_item.product.quantity += old_item.quantity
                    old_item.product.product.save()
                    old_item.product.save()
                old_items.delete()
            for data in items:
                SaleOrderItems.objects.create(saleorder=saleorder, **data)

            from inventory.services import SaleOrderService
            SaleOrderService(saleorder).process()

            messages.success(request, f"Sale Order #{saleorder.id} {'updated' if instance else 'created'} successfully.")
            return redirect('inventory:saleorder_list')

    return render(request, 'admin_pages/saleorder_form.html', {
        'form': form,
        'saleorder': instance,
        'saleorder_items': saleorder_items,
        'title': 'Edit Sale Order' if instance else 'Add Sale Order',
        'doctors': Doctor.objects.all(),
        'form_uuid': form_uuid,
    })


@require_GET
def order_product_search_api(request):
    q = request.GET.get('q', '')

    results = Product.objects.filter(title__icontains=q)[:10]

    data = []
    for product in results:
        data.append({
            'id': product.id,
            'title': f"{product.title} - Price: {product.unit_price}",
            'price': float(product.unit_price),
        })

    return JsonResponse(data, safe=False)


def order_create_or_update_view(request, pk=None):
    instance = get_object_or_404(Order, pk=pk) if pk else None
    permission = f'inventory.{"change" if instance else "add"}_order'
    if not request.user.has_perm(permission):
        raise PermissionDenied

    form = OrderForm(request.POST or None, instance=instance)
    order_items = []

    if instance:
        order_items = OrderItem.objects.filter(
            order=instance).select_related('product')

    if request.method == 'POST' and form.is_valid():
        order = form.save(commit=False)
        if not instance:
            order.created_by = request.user
        order.updated_by = request.user
        order.save()

        if instance:
            OrderItem.objects.filter(order=order).delete()

        total_forms = int(request.POST.get('form-TOTAL_FORMS', 0))
        for i in range(total_forms):
            product_id = request.POST.get(f'form-{i}-product')
            quantity = Decimal(request.POST.get(f'form-{i}-quantity') or 0)
            unit_price = Decimal(request.POST.get(f'form-{i}-unit_price') or 0)

            if product_id and quantity:
                OrderItem.objects.create(
                    order=order,
                    product_id=product_id,
                    quantity=quantity,
                    unit_price=unit_price,
                )

        order.save()  # Save again to trigger any post-processing logic

        messages.success(
            request,
            f"Order #{order.id} {'updated' if instance else 'created'} successfully."
        )
        return redirect('inventory:order_list')

    return render(request, 'admin_pages/order_form.html', {
        'form': form,
        'order': instance,
        'order_items': order_items,
        'title': 'Edit Order' if instance else 'Add Order',
    })


def _update_supplier_account(order, receive_order):
    """Create or update SupplierAccount entry when an order is received."""
    items = receive_order.receivedorderitem_set.all()
    total = sum(
    # Only the purchased price should contribute to the supplier account.
    # Cargo charges are handled separately and must not affect the
    # supplier's balance when an order is received.
        item.purchased_price * item.received_quantity
        for item in items
    )
    if total == 0:
        return

    prev = (
        SupplierAccount.objects.filter(supplier=order.order_to)
        .order_by('-id')
        .values('current_balance')
        .first()
    )
    previous_balance = prev['current_balance'] if prev else Decimal('0')
    credit = Decimal(total)
    debit = Decimal('0')
    current_balance = previous_balance + credit - debit

    SupplierAccount.objects.update_or_create(
        supplier=order.order_to,
        tx_number=f'PO00-{order.id}',
        defaults={
            'details': f'Purchase Order #{order.id}',
            'previous_balance': previous_balance,
            'debit': debit,
            'credit': credit,
            'current_balance': current_balance,
        },
    )


def _record_cargo_expense(order, receive_order, user):
    """Log cargo charges as an expense under the Belti category."""
    total_cargo = sum(
        item.carge_charges for item in receive_order.receivedorderitem_set.all()
    )
    if total_cargo <= 0:
        return

    category = ExpenseCategory.objects.filter(title__iexact="Belti").first()
    if not category:
        return

    employment = Employment.objects.first()
    if not employment:
        return

    AddExpense.objects.update_or_create(
        description=f"Cargo charges for PO#{order.id} for: {order.cargo}",
        defaults={
            "expense_category": category,
            "spent_by": user.get_full_name() or user.username,
            "approved_by": employment,
            "amount": total_cargo,
            "currency": order.currency,
            "from_account": AddExpense.CASH_IN_HAND,
            "remarks": "Auto-generated from order receipt",
            "date": now().date(),
            "created_by": user,
            "updated_by": user,
        },
    )

def order_receive_view(request, order_id):
    order = get_object_or_404(Order, id=order_id)
    order_items = order.orderitem_set.select_related('product')

    # ✅ Get the existing ReceiveOrder (created by Order.save)
    receive_order = ReceiveOrder.objects.filter(order=order).first()

    if not receive_order:
        messages.error(request, "ReceiveOrder does not exist for this order.")
        return redirect('inventory:order_list')

    if request.method == 'POST':
        # Update receive_order fields
        receive_order.remarks = request.POST.get('remarks', '')
        receive_order.updated_by = request.user
        receive_order.save()

        # Clear old items
        receive_order.receivedorderitem_set.all().delete()

        total_ordered = 0
        total_received = 0
        total_forms = int(request.POST.get('form-TOTAL_FORMS', 0))

        for i in range(total_forms):
            product_id = request.POST.get(f'form-{i}-product')
            batch = request.POST.get(f'form-{i}-batch')
            expiry = request.POST.get(f'form-{i}-expiry_date')
            purchased = Decimal(request.POST.get(
                f'form-{i}-purchased_price') or 0)
            received = int(request.POST.get(
                f'form-{i}-received_quantity') or 0)
            damaged = int(request.POST.get(f'form-{i}-damaged_quantity') or 0)
            cargo = Decimal(request.POST.get(f'form-{i}-cargo_charges') or 0)
            sale = Decimal(request.POST.get(f'form-{i}-sale_rate') or 0)

            order_item = order_items.get(product_id=product_id)

            ReceivedOrderItem.objects.create(
                received_order=receive_order,
                product=order_item.product,
                batch_details=batch,
                expiry_date=expiry,
                ordered_quantity=order_item.quantity,
                purchased_price=purchased,
                received_quantity=received,
                damaged_quantity=damaged,
                carge_charges=cargo,
                sale_rate=sale
            )

            total_ordered += order_item.quantity
            total_received += received

        # ✅ Trigger ProductBatches creation after all items are processed
        receive_order.save()


        _update_supplier_account(order, receive_order)
        _record_cargo_expense(order, receive_order, request.user)

        # ✅ Set order status to Completed if all items received
        if total_received >= total_ordered:
            order.order_status = Order.STATUS_COMPLETED
            order.updated_by = request.user
            order.save(update_fields=['order_status', 'updated_by'])

        messages.success(request, f"Order #{order.id} received successfully.")
        return redirect('inventory:order_list')

    return render(request, 'admin_pages/order_receive_form.html', {
        'order': order,
        'order_items': order_items,
        'receive_order': receive_order,
    })
@login_required
@permission_required("inventory.view_receiveorder", raise_exception=True)
def order_receive_details_view(request, order_id):
    order = get_object_or_404(Order, id=order_id)
    receive_order = get_object_or_404(ReceiveOrder, order=order)
    received_items = receive_order.receivedorderitem_set.select_related(
        'product')

    total_purchase_value = 0
    total_expected_sale_value = 0

    for item in received_items:
        returned_qty = item.supplierreturnitem_set.aggregate(
            total=Sum('quantity'))['total'] or 0
        item.returned_quantity = returned_qty
        item.saleable_qty = (item.received_quantity or 0) - \
            (item.damaged_quantity or 0) - returned_qty
        # Cargo charges are tracked separately and should not influence the
        # supplier's payable amount. Therefore, use only the purchased price
        # when calculating totals.
        purchase_unit_price = (item.purchased_price or 0)

        item.line_purchase_total = (
            (item.received_quantity or 0) - returned_qty
        ) * purchase_unit_price
        item.line_sale_total = item.saleable_qty * (item.sale_rate or 0)

        total_purchase_value += item.line_purchase_total
        total_expected_sale_value += item.line_sale_total

    return render(request, 'admin_pages/order_receive_details.html', {
        'order': order,
        'receive_order': receive_order,
        'received_items': received_items,
        'purchase_total': total_purchase_value,
        'sale_total': total_expected_sale_value,

    })

@login_required
@permission_required("inventory.change_receiveorder", raise_exception=True)
def order_return_view(request, order_id):
    order = get_object_or_404(Order, id=order_id)
    if order.order_status != Order.STATUS_COMPLETED:
        messages.error(request, "Cannot return items before the order is fully received.")
        return redirect("inventory:order_list")
    receive_order = get_object_or_404(ReceiveOrder, order=order)
    items = list(receive_order.receivedorderitem_set.select_related("product"))
    for it in items:
        returned = it.supplierreturnitem_set.aggregate(total=Sum("quantity"))["total"] or 0
        batch = ProductBatches.objects.filter(product=it.product, batch_number=it.batch_details).first()
        available = batch.quantity if batch else 0
        sold = (it.received_quantity or 0) - (available + returned)
        it.sold = max(sold, 0)
        it.returned = returned
        it.returnable = min(available, (it.received_quantity or 0) - returned)
    if request.method == "POST":
        remarks = request.POST.get("remarks", "")
        returns = []
        errors = []
        for it in items:
            qty = int(request.POST.get(f"qty-{it.id}", 0) or 0)
            if qty <= 0:
                continue
            if qty > it.returnable:
                errors.append(f"Return qty for {it.product.title} exceeds available {it.returnable}.")
            else:
                returns.append((it, qty))
        if errors:
            for e in errors:
                messages.error(request, e)
        elif not returns:
            messages.warning(request, "No quantities entered for return.")
        else:
            with transaction.atomic():
                supplier_return = SupplierReturn.objects.create(
                    receive_order=receive_order,
                    remarks=remarks,
                    created_by=request.user,
                    updated_by=request.user,
                )
                total_amount = Decimal("0")
                for it, qty in returns:
                    SupplierReturnItem.objects.create(
                        supplier_return=supplier_return,
                        received_item=it,
                        quantity=qty,
                    )
                    it.product.inventory -= qty
                    it.product.save()
                    batch = ProductBatches.objects.filter(product=it.product, batch_number=it.batch_details).first()
                    if batch:
                        batch.quantity -= qty
                        batch.save()
                    # Exclude cargo charges from the supplier's account when
                    # calculating the return amount.
                    total_amount += it.purchased_price * qty
                prev = SupplierAccount.objects.filter(supplier=order.order_to).order_by("-id").first()
                prev_bal = prev.current_balance if prev else Decimal("0")
                SupplierAccount.objects.create(
                    supplier=order.order_to,
                    tx_number=f"RET-PO{order.id}-{supplier_return.id}",
                    details=f"Return for PO#{order.id}",
                    previous_balance=prev_bal,
                    debit=total_amount,
                    credit=Decimal("0"),
                    current_balance=prev_bal - total_amount,
                )
                messages.success(request, f"Return #{supplier_return.id} created for Order #{order.id}.")
                return redirect("inventory:order_list")
    return render(request, "admin_pages/order_return_form.html", {
        "order": order,
        "items": items,
    })


def generic_list_view(
    request,
    model,
    template_name,
    context_name,
    search_fields=None,
    items_per_page=10,
    permission=None,
    redirect_url_name='inventory:admin_page',
    extra_filters=None  # 👈 NEW: optional dict
):
    # 🔐 Permission check
    if permission is None:
        app_label = model._meta.app_label
        model_name = model._meta.model_name
        permission = f'{app_label}.view_{model_name}'

    if not request.user.has_perm(permission):
        messages.error(
            request, "You do not have permission to access this page.")
        return redirect(redirect_url_name)

    queryset = model.objects.all()
    query = request.GET.get('q', '')

    if query and search_fields:
        filters = Q()
        for field in search_fields:
            filters |= Q(**{f"{field}__icontains": query})
        queryset = queryset.filter(filters)

    sort = request.GET.get('sort', 'newest')
    try:
        model._meta.get_field('created_at')
        if sort == 'oldest':
            queryset = queryset.order_by('created_at')
        else:
            queryset = queryset.order_by('-created_at')
    except FieldDoesNotExist:
        # Don't apply ordering if the model doesn't have 'created_at'
        pass

    # ✅ Apply extra filters like status
    if extra_filters:
        for key, field in extra_filters.items():
            value = request.GET.get(key)
            if value:
                queryset = queryset.filter(**{field: value})

    paginator = Paginator(queryset, items_per_page)
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)

    return render(request, template_name, {
        context_name: page_obj,
        'search_query': query,
        'model_verbose_name': model._meta.verbose_name.title(),
        'sort': sort,
    })


def generic_delete_view(
    request,
    model,
    pk,
    success_url,
    template_name,
    object_name_field='name',
    context_name='object',
    permission=None
):
    obj = get_object_or_404(model, pk=pk)
    object_label = getattr(obj, object_name_field, str(obj))
    model_name = model._meta.verbose_name.title()

    # 🔐 Permission check (dynamically determined if not passed)
    if permission is None:
        app_label = model._meta.app_label
        model_name_lower = model._meta.model_name
        permission = f"{app_label}.delete_{model_name_lower}"

    if not request.user.has_perm(permission):
        raise PermissionDenied

    related_items = []

    if request.method == 'POST':
        try:
            obj.delete()
            messages.error(
                request,
                f'{model_name} "<strong>{escape(object_label)}</strong>" deleted successfully.'
            )
            return redirect(success_url)
        except ProtectedError as e:
            for related in e.protected_objects:
                related_items.append({
                    'model': related._meta.verbose_name.title(),
                    'name': str(related)
                })
            messages.error(
                request,
                f'Cannot delete "<strong>{escape(object_label)}</strong>" because it is linked to other items.'
            )

    return render(request, template_name, {
        context_name: obj,
        'object': obj,
        'related_items': related_items,
        'model_verbose_name': model_name,
    })


def bulk_delete_view(request, model, template_name, success_url, object_name_field='name', permission=None):
    if request.method != 'POST':
        messages.warning(request, "Invalid request method.")
        return redirect(success_url)

    ids = request.POST.getlist('selected_ids')

    if not ids:
        messages.warning(request, "No items selected.")
        return redirect(success_url)

    # 🔐 Permission check
    if permission is None:
        app_label = model._meta.app_label
        model_name = model._meta.model_name
        # fallback to model-level delete permission
        permission = f'{app_label}.delete_{model_name}'

    if not request.user.has_perm(permission):
        raise PermissionDenied

    objects = get_list_or_404(model, id__in=ids)

    if 'confirm' in request.POST:
        try:
            count = len(objects)
            model.objects.filter(id__in=ids).delete()
            messages.success(request, f'{count} item(s) deleted successfully.')
            return redirect(success_url)
        except ProtectedError as e:
            protected_objects = list(e.protected_objects)[:5]
            related_info = ", ".join(
                f"{obj._meta.verbose_name.title()}: <strong>{escape(str(obj))}</strong>"
                for obj in protected_objects
            )
            messages.error(
                request,
                f'Cannot delete item(s) because they are linked to: {related_info}'
                + ("..." if len(e.protected_objects) > 5 else "")
            )
            return redirect(success_url)

    return render(request, template_name, {
        'objects': objects,
        'model_verbose_name': model._meta.verbose_name.title(),
        'object_name_field': object_name_field,
    })


def generic_form_view(
    request,
    model,
    form_class,
    template_name,
    success_url,
    title,
    object_instance=None,
    object_name_field='name',
    permission=None
):
    instance = object_instance

    # 🔐 Dynamic permission detection
    if permission is None:
        app_label = model._meta.app_label
        model_name = model._meta.model_name
        action = 'change' if instance else 'add'
        permission = f"{app_label}.{action}_{model_name}"

    if not request.user.has_perm(permission):
        raise PermissionDenied

    # ✅ Handle file uploads correctly
    form = form_class(request.POST or None,
                      request.FILES or None, instance=instance)

    if form.is_valid():
        # Save with user tracking
        obj = form.save(commit=False)

        # Set created_by if applicable and new object
        if not instance and hasattr(obj, 'created_by'):
            obj.created_by = request.user

        # Always update updated_by if the field exists
        if hasattr(obj, 'updated_by'):
            obj.updated_by = request.user

        obj.save()
        form.save_m2m()  # For many-to-many relationships

        action = "updated" if instance else "created"
        label = getattr(obj, object_name_field, str(obj))
        model_verbose = model._meta.verbose_name.title()

        messages.success(
            request,
            f'{model_verbose} "<strong>{escape(label)}</strong>" {action} successfully.'
        )
        return redirect(success_url)

    return render(request, template_name, {
        'form': form,
        'title': title
    })


@login_required
@permission_required('finance.view_addexpense', raise_exception=True)
def expense_print_view(request, pk):
    expense = get_object_or_404(AddExpense, pk=pk)
    context = {
        'expense': expense,
        'title': f'Expense Receipt #{expense.id}',
        'now': now()
    }
    return render(request, 'admin_pages/expense_print.html', context)


@permission_required('inventory.view_consumeraccount', raise_exception=True)
def consumer_account_list_view(request, consumer_id):
    consumer = get_object_or_404(Consumer, id=consumer_id)
    accounts = ConsumerAccount.objects.filter(
        consumer=consumer).order_by('-created_at')

    return render(request, 'admin_pages/consumer_account_list.html', {
        'consumer': consumer,
        'accounts': accounts,
        'title': f'{consumer.name} - Account History'
    })


@permission_required('inventory.view_supplieraccount', raise_exception=True)
def supplier_account_list_view(request, supplier_id):
    supplier = get_object_or_404(Supplier, id=supplier_id)
    accounts = SupplierAccount.objects.filter(
        supplier=supplier).order_by('-created_at')

    return render(request, 'admin_pages/supplier_account_list.html', {
        'supplier': supplier,
        'accounts': accounts,
        'title': f'{supplier.name} - Account History',
    })


def supplier_payment_slip_view(request, account_id):
    account = get_object_or_404(SupplierAccount, id=account_id)
    payment = None
    supplier_return = None
    return_items = []
    if account.tx_number.startswith('SP00-'):
        try:
            payment_id = int(account.tx_number.split('SP00-')[1])
            payment = Payment_To_Supplier.objects.filter(id=payment_id).first()
        except (ValueError, IndexError):
            payment = None
    elif account.tx_number.startswith('RET-PO'):
        try:
            return_id = int(account.tx_number.split('-')[-1])
            supplier_return = (
                SupplierReturn.objects
                .select_related('receive_order__order')
                .prefetch_related('supplierreturnitem_set__received_item__product')
                .get(id=return_id)
            )
            for item in supplier_return.supplierreturnitem_set.all():
                # Cargo charges are excluded from supplier payments/returns.
                price = item.received_item.purchased_price
                return_items.append({
                    'product': item.received_item.product,
                    'quantity': item.quantity,
                    'price': price,
                    'total': price * item.quantity,
                })
        except (ValueError, IndexError, SupplierReturn.DoesNotExist):
            supplier_return = None
            return_items = []
    return render(request, 'admin_pages/supplier_payment_slip.html', {
        'account': account,
        'payment': payment,
        'supplier_return': supplier_return,
        'return_items': return_items,
        'title': 'Supplier Payment Slip',
    })

def consumer_payment_slip_view(request, account_id):
    account = get_object_or_404(ConsumerAccount, id=account_id)
    payment = None
    # Payment entries create consumer account records with tx_number like
    # "CB00-<payment_id>". Parse the ID to fetch the related payment record so
    # we can display its receipt number on the slip.
    if account.tx_number.startswith('CB00-'):
        try:
            payment_id = int(account.tx_number.split('CB00-')[1])
            payment = Payment_From_Consumer.objects.filter(id=payment_id).first()
        except (ValueError, IndexError):
            payment = None

    return render(request, 'admin_pages/consumer_payment_slip.html', {
        'account': account,
        'payment': payment,
        'title': 'Consumer Payment Slip'
    })


@login_required
def supplier_balance_opening_view(request, consumer_id):
    consumer = get_object_or_404(Consumer, id=consumer_id)
    tx_number = f'BalanceOpening{consumer.name.replace(" ", "")}'

    # Check for existing opening entry
    instance = ConsumerAccount.objects.filter(
        consumer=consumer,
        tx_number=tx_number
    ).first()

    if request.method == 'POST':
        form = BalanceOpeningForm(request.POST, instance=instance)
        if form.is_valid():
            account = form.save(commit=False)
            account.consumer = consumer
            account.tx_number = tx_number
            account.previous_balance = 0
            account.debit = 0
            account.current_balance = account.credit
            account.updated_by = request.user
            if not instance:
                account.created_by = request.user
            account.save()

            msg = "updated" if instance else "created"
            messages.success(
                request, f"Opening balance for {consumer.name} {msg} successfully.")
            return redirect('inventory:consumer_account_list', consumer.id)
    else:
        form = BalanceOpeningForm(instance=instance)

    return render(request, 'admin_pages/balance_opening_form.html', {
        'form': form,
        'consumer': consumer,
        'title': f'{"Edit" if instance else "Add"} Opening Balance for {consumer.name}'
    })


@login_required
def doctor_commission_list_view(request, doctor_id):
    doctor = get_object_or_404(Doctor, id=doctor_id)
    commissions = DoctorCommission.objects.filter(
        doctor=doctor).order_by('-placed_at')

    return render(request, 'admin_pages/doctor_commission_list.html', {
        'doctor': doctor,
        'commissions': commissions,
        'title': f'Commission Records for {doctor.name}'
    })

@login_required
@permission_required('inventory.add_doctorproductcommission', raise_exception=True)
def doctor_define_commission_view(request, doctor_id):
    """Define commissions for all products for a specific doctor."""
    doctor = get_object_or_404(Doctor, id=doctor_id)
    products = Product.objects.all().order_by('title')

    if request.method == 'POST':
        for product in products:
            raw = request.POST.get(f'commission_{product.id}', '').strip()
            if raw == '':
                continue
            try:
                commission_value = Decimal(raw)
            except Exception:
                continue

            DoctorProductCommission.objects.update_or_create(
                doctor=doctor,
                product=product,
                defaults={'commission': commission_value}
            )

        messages.success(
            request,
            f'Commissions for <strong>{escape(doctor.name)}</strong> saved.'
        )
        return redirect('inventory:doctor_list')

    existing = DoctorProductCommission.objects.filter(doctor=doctor)
    commission_map = {c.product_id: c.commission for c in existing}

    return render(request, 'admin_pages/doctor_define_commission_form.html', {
        'doctor': doctor,
        'products': products,
        'commission_map': commission_map,
        'title': f'Define Commission for {doctor.name}',
    })


@login_required
@permission_required('inventory.add_consumerproductdiscount', raise_exception=True)
def consumer_define_discount_view(request, consumer_id):
    """Define discounts for all products for a specific consumer."""
    consumer = get_object_or_404(Consumer, id=consumer_id)
    products = Product.objects.all().order_by('title')

    if request.method == 'POST':
        for product in products:
            raw = request.POST.get(f'discount_{product.id}', '').strip()
            price_raw = request.POST.get(f'price_{product.id}', '').strip()
            if raw == '' and price_raw == '':
                continue
            try:
                discount_value = int(raw or 0)
            except Exception:
                discount_value = 0
            try:
                price_value = Decimal(price_raw) if price_raw != '' else None
            except Exception:
                price_value = None

            ConsumerProductDiscount.objects.update_or_create(
                consumer=consumer,
                product=product,
                defaults={'discount': discount_value, 'unit_price': price_value}
            )

        messages.success(
            request,
            f'Discounts for <strong>{escape(consumer.name)}</strong> saved.'
        )
        return redirect('inventory:consumer_list')

    existing = ConsumerProductDiscount.objects.filter(consumer=consumer)
    discount_map = {c.product_id: c.discount for c in existing}
    price_map = {c.product_id: c.unit_price for c in existing}

    return render(request, 'admin_pages/consumer_define_discount_form.html', {
        'consumer': consumer,
        'products': products,
        'discount_map': discount_map,
        'title': f'Define Discount for {consumer.name}',
        'price_map': price_map,
    })


def order_details(request, pk):
    company_profile = CompanyProfile.objects.first()
    current_date = date.today()
    order = get_object_or_404(Order, pk=pk)
    order_items = OrderItem.objects.filter(order=order)

    total_amount = 0

    for order_item in order_items:
        order_item.amount = order_item.quantity * order_item.unit_price
        total_amount += order_item.amount

    remaining_amount = total_amount - order.amount_paid

    context = {
        'order': order,
        'order_items': order_items,
        'current_date': current_date,
        'company_profile': company_profile,
        'total_amount': total_amount,
        'remaining_amount': remaining_amount  # Add remaining_amount to the context
    }

    return render(request, 'inventory/order_details.html', context)


# RECEIVED ORDER DETAILS PRINT


def received_order_details(request, pk):
    company_profile = CompanyProfile.objects.first()
    current_date = date.today()
    order = get_object_or_404(ReceiveOrder, pk=pk)
    order_items = ReceivedOrderItem.objects.filter(received_order=order)

    total_amount = 0

    for order_item in order_items:
        order_item.amount = order_item.received_quantity * order_item.get_GP_Price()
        total_amount += order_item.amount
        amount_paid = order.order.amount_paid * order.order.exchange_rate
        remaining_amount = total_amount-amount_paid

    context = {
        'order': order,
        'order_items': order_items,
        'current_date': current_date,
        'company_profile': company_profile,
        'total_amount': total_amount,
        'amount_paid': amount_paid,
        'remaining_amount': remaining_amount,
    }

    return render(request, 'inventory/received_order_details.html', context)

# sale invoice print view

@login_required
@permission_required('inventory.add_payment_from_consumer', raise_exception=True)
def consumer_payment_create_view(request, consumer_id):
    consumer = get_object_or_404(Consumer, id=consumer_id)

    if request.method == 'POST':
        form = PaymentFromConsumerForm(request.POST)
        if form.is_valid():
            payment = form.save(commit=False)
            payment.consumer = consumer
            payment.created_by = request.user
            payment.updated_by = request.user
            payment.save()
            return redirect('inventory:consumer_account_list', consumer_id=consumer.id)
    else:
        form = PaymentFromConsumerForm()

    # Calculate total credit and debit
    account_summary = ConsumerAccount.objects.filter(consumer=consumer).aggregate(
        total_credit=Sum('credit'),
        total_debit=Sum('debit')
    )

    credit = account_summary['total_credit'] or 0
    debit = account_summary['total_debit'] or 0
    current_balance = credit - debit

    return render(request, 'admin_pages/consumer_payment_form.html', {
        'form': form,
        'consumer': consumer,
        'title': 'Add Consumer Payment',
        'balance': current_balance,

    })



@login_required
@permission_required('inventory.add_payment_to_supplier', raise_exception=True)
def supplier_payment_create_view(request, supplier_id):
    supplier = get_object_or_404(Supplier, id=supplier_id)

    if request.method == 'POST':
        form = PaymentToSupplierForm(request.POST)
        if form.is_valid():
            payment = form.save(commit=False)
            payment.supplier = supplier
            payment.created_by = request.user
            payment.updated_by = request.user
            payment.save()
            return redirect('inventory:supplier_account_list', supplier_id=supplier.id)
    else:
        form = PaymentToSupplierForm()

    account_summary = SupplierAccount.objects.filter(supplier=supplier).aggregate(
        total_credit=Sum('credit'),
        total_debit=Sum('debit')
    )

    credit = account_summary['total_credit'] or 0
    debit = account_summary['total_debit'] or 0
    current_balance = credit - debit

    return render(request, 'admin_pages/supplier_payment_form.html', {
        'form': form,
        'supplier': supplier,
        'title': 'Add Supplier Payment',
        'balance': current_balance,

    })


from decimal import Decimal
from django.db import transaction
from django.db.models import Sum
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.utils import timezone

from .models import ConsumerReturn, ConsumerReturnItem
from inventory.models import SaleOrder, SaleOrderItems
from finance.models import ConsumerAccount, AccountPayableReceivable, DoctorCommission, Currency


@login_required
def consumer_return_create_view(request, saleorder_id):
    saleorder = get_object_or_404(SaleOrder, id=saleorder_id)
    items = list(saleorder.saleorderitems_set.select_related('product__product'))

    # 1️⃣ Compute per-line returned & remaining, plus total already refunded
    total_already_refunded = Decimal(0)
    for it in items:
        ret = it.consumerreturnitem_set.aggregate(total=Sum('quantity'))['total'] or Decimal(0)
        it.returned = ret
        it.remaining = it.quantity - ret
        total_already_refunded += ret
    sum_remaining = sum(it.remaining for it in items)

    if request.method == 'POST':
        remarks = request.POST.get('remarks', '')
        errors = []
        returns = []                   
        current_returned = Decimal(0)

        # 2️⃣ Validate requested return quantities
        for it in items:
            raw = request.POST.get(f'qty-{it.id}', '0') or '0'
            try:
                qty = Decimal(raw)
            except Exception:
                errors.append(f"Invalid quantity for {it.product.product.title}.")
                continue

            if qty <= 0:
                continue
            if qty > it.remaining:
                errors.append(
                    f"Return qty for {it.product.product.title} exceeds available {it.remaining}."
                )
            else:
                returns.append((it, qty))
                current_returned += qty

        if errors:
            for e in errors:
                messages.error(request, e)
        elif not returns:
            messages.warning(request, "No quantities entered for return.")
        else:
            with transaction.atomic():
                # Get a currency instance
                currency_instance = Currency.objects.first()

                # 3️⃣ Create ConsumerReturn header
                consumer_return = ConsumerReturn.objects.create(
                    saleorder=saleorder,
                    remarks=remarks,
                    created_by=request.user,
                    updated_by=request.user,
                )

                # 4️⃣ Process each return line: rollback inventory & build totals
                total_return_amount = Decimal(0)
                doc_totals = {}  # doctor_id -> total commission to debit

                for it, qty in returns:
                    # a) line record
                    ConsumerReturnItem.objects.create(
                        consumer_return=consumer_return,
                        saleorder_item=it,
                        quantity=qty,
                    )

                    # b) rollback inventory
                    batch = it.product
                    batch.quantity += qty
                    batch.product.inventory += qty
                    batch.product.save()
                    batch.save()

                    # c) accumulate financials
                    total_return_amount += qty * it.net_price
                    doc_totals.setdefault(it.doctor_id, Decimal(0))
                    doc_totals[it.doctor_id] += it.commission * qty

                # 5️⃣ ConsumerAccount debit entry
                ca_tx = f"CRT-SO{saleorder.id}-R{consumer_return.id}"
                prev_acc = ConsumerAccount.objects.filter(consumer=saleorder.consumer).order_by('-id').first()
                prev_bal = prev_acc.current_balance if prev_acc else Decimal(0)
                new_bal = prev_bal - total_return_amount
                ConsumerAccount.objects.create(
                    consumer=saleorder.consumer,
                    tx_number=ca_tx,
                    details=f"Return from SO#{saleorder.id} (Return #{consumer_return.id})",
                    previous_balance=prev_bal,
                    debit=total_return_amount,
                    credit=Decimal(0),
                    current_balance=new_bal,
                )

                # 6️⃣ Reverse A/R entry
                ar_tx = f"AR-SO{saleorder.id}-R{consumer_return.id}"
                AccountPayableReceivable.objects.create(
                    date=timezone.now().date(),
                    account_type='A/R',
                    tx_number=ar_tx,
                    description=f"Return SO#{saleorder.id}",
                    amount_in=Decimal(0),
                    amount_out=total_return_amount,
                    currency=currency_instance,
                    remarks='Sale return',
                )

                # 7️⃣ DoctorCommission & A/P entries per doctor
                for doc_id, comm_sum in doc_totals.items():
                    prev_dc = DoctorCommission.objects.filter(doctor_id=doc_id).order_by('-id').first()
                    prev_dc_bal = prev_dc.current_balance if prev_dc else Decimal(0)
                    new_dc_bal = prev_dc_bal - comm_sum

                    DoctorCommission.objects.create(
                        doctor_id=doc_id,
                        details=f"Return from SO#{saleorder.id} (Return #{consumer_return.id})",
                        previous_balance=prev_dc_bal,
                        debit=comm_sum,
                        credit=Decimal(0),
                        current_balance=new_dc_bal,
                        remarks='Returned commission',
                    )

                    ap_tx = f"AP-SO{saleorder.id}-R{consumer_return.id}-D{doc_id}"
                    AccountPayableReceivable.objects.create(
                        date=timezone.now().date(),
                        account_type='A/P',
                        tx_number=ap_tx,
                        description=f"Doctor return for SO#{saleorder.id}",
                        amount_in=Decimal(0),
                        amount_out=comm_sum,
                        currency=currency_instance,
                        remarks='Doctor commission return',
                    )

            # 8️⃣ Success message with both current & cumulative totals
            messages.success(
                request,
                f"Return #{consumer_return.id} created for SO#{saleorder.id}. "
                f"Returned now: {current_returned} pcs. "
                f"Total refunded so far: {total_already_refunded + current_returned} pcs."
            )
            return redirect('inventory:saleorder_list')

    # 9️⃣ Render the form
    return render(request, 'admin_pages/consumer_return_form.html', {
        'saleorder': saleorder,
        'saleorder_items': items,
        'remarks': request.POST.get('remarks', ''),
        'title': 'Consumer Return',
        'total_already_refunded': total_already_refunded,
        'sum_remaining': sum_remaining,
    })

def generate_invoice(request, pk):
    company_profile = CompanyProfile.objects.first()
    try:
        sale_order = SaleOrder.objects.get(pk=pk)
        sale_order_items = sale_order.saleorderitems_set.all()

        total = sum(item.total_amount for item in sale_order_items)
        gross_amount = sum(
            item.quantity * item.price for item in sale_order_items)
        total_discount = sum((item.price * item.discount / 100)
                             * item.quantity for item in sale_order_items)
        subtotal = gross_amount - total_discount
        brt_amount = (subtotal * sale_order.brt_rate / 100) if sale_order.brt_rate else 0
        grand_total = subtotal - brt_amount

        formatted_created_at = formats.date_format(
            sale_order.created_at, "Y F d")

        # Fetch the ConsumerAccount for the given consumer
        consumer_account = ConsumerAccount.objects.filter(
            consumer=sale_order.consumer).order_by('-id').first()

        # Calculate the previous balance and net balance
        previous_balance = consumer_account.current_balance - \
            grand_total if consumer_account else 0
        net_balance = previous_balance + grand_total

    except SaleOrder.DoesNotExist:
        return render(request, 'inventory/invoice.html', context={'error': 'Invoice not found'})

    context = {
        'sale_order': sale_order,
        'total': total,
        'gross_amount': gross_amount,
        'total_discount': total_discount,
        'brt_amount': brt_amount,
        'grand_total': grand_total,
        'formatted_created_at': formatted_created_at,
        'company_profile': company_profile,
        'previous_balance': previous_balance,
        'net_balance': net_balance,
    }
    return render(request, 'inventory/invoice.html', context)


# consumer balance sheet print


def print_consumer_balance_sheet(request, pk):
    if request.method == 'POST':
        start_date = request.POST.get('start_date')
        end_date = request.POST.get('end_date')

        consumer = Consumer.objects.get(id=pk)
        consumer_balance = ConsumerAccount.objects.filter(
            consumer=consumer).order_by('tx_date')

        if start_date and end_date:
            try:
                start_date = parse(start_date).date()
                end_date = parse(end_date).date()
            except ValueError:
                start_date = None
                end_date = None
            else:
                consumer_balance = consumer_balance.filter(
                    tx_date__range=(start_date, end_date))

        if not consumer_balance:
            context = {
                'consumer': consumer,
                'start_date': start_date,
                'end_date': end_date,

            }
            return render(request, 'inventory/consumer_no_balance.html', context)

        totals = consumer_balance.aggregate(
            total_debit=Sum('debit'),
            total_credit=Sum('credit'),
            total_balance=Sum('current_balance')
        )

        from_date = consumer_balance.first().tx_date
        to_date = consumer_balance.last().tx_date
        net_balance = totals['total_credit'] - totals['total_debit']

        context = {
            'consumer': consumer,
            'from_date': from_date,
            'to_date': to_date,
            'totals': totals,
            'balance': consumer_balance,
            'total_debit': totals['total_debit'],
            'total_credit': totals['total_credit'],
            'net_balance': net_balance,
            'total_balance': totals['total_balance'],
        }
        return render(request, 'inventory/print_consumer_balance.html', context)

    context = {
        'pk': pk
    }
    return render(request, 'inventory/date_parameter_consumer_balance.html', context)
# doctor balance print


def print_doctor_balance_sheet(request, pk):
    doctor = Doctor.objects.get(id=pk)
    doctor_balance = DoctorCommission.objects.filter(
        doctor=doctor).order_by('placed_at')

    if not doctor_balance:
        context = {
            'docotr': doctor,
        }
        return render(request, 'inventory/no_commission_details_report.html', context)

    # Aggregate the debit, credit, and current_balance fields for the queryset
    totals = doctor_balance.aggregate(
        total_debit=Sum('debit'),
        total_credit=Sum('credit'),
        total_balance=Sum('current_balance')
    )
    from_date = doctor_balance.first().placed_at
    to_date = doctor_balance.last().placed_at
    net_balance = totals['total_credit'] - totals['total_debit']

    context = {
        'doctor': doctor,
        'from_date': from_date,
        'to_date': to_date,
        'totals': totals,
        'balance': doctor_balance,
        'total_debit': totals['total_debit'],
        'total_credit': totals['total_credit'],
        'net_balance': net_balance,
        'total_balance': totals['total_balance'],
    }

    return render(request, 'inventory/print_doctor_balance.html', context)
# consumer payment receipt to company


def received_payment_voucher_from_consumer(request, pk):
    # Get the company profile object
    company_profile = CompanyProfile.objects.first()

    # Retrieve the Payment object and related customer and creator objects
    payment = get_object_or_404(Payment_From_Consumer.objects.select_related(
        'consumer', 'received_by', 'created_by'), pk=pk)

    # Create a context dictionary for the template
    context = {
        'supplier_details': payment,
        'company_profile': company_profile,
    }

    # Render the template with the context dictionary
    return render(request, 'inventory/received_payment_voucher_from_consumer.html', context)



@login_required
@permission_required('inventory.view_payment_from_consumer', raise_exception=True)
def cash_collection_list_view(request):
    payments = Payment_From_Consumer.objects.select_related('consumer', 'received_by').order_by('-created_at')
    start_date = parse_date_safe(request.GET.get('start_date'))
    end_date = parse_date_safe(request.GET.get('end_date'))
    consumer_id = request.GET.get('consumer')

    if start_date:
        start_datetime = datetime.combine(start_date, time.min)
        payments = payments.filter(created_at__gte=start_datetime)
    if end_date:
        end_datetime = datetime.combine(end_date, time.max)
        payments = payments.filter(created_at__lte=end_datetime)
    if consumer_id:
        payments = payments.filter(consumer_id=consumer_id)
    total_amount = payments.aggregate(total=Sum('amount_paid'))['total'] or 0
    today_total = Payment_From_Consumer.objects.filter(created_at__date=date.today()).aggregate(total=Sum('amount_paid'))['total'] or 0
    consumers = Consumer.objects.all()
    context = {
        'payments': payments,
        'total_amount': total_amount,
        'today_total': today_total,
        'consumers': consumers,
        'selected_consumer_id': int(consumer_id) if consumer_id else None,
        'start_date': start_date.isoformat() if start_date else '',
        'end_date': end_date.isoformat() if end_date else '',
    }
    return render(request, 'admin_pages/cash_collection_list.html', context)

def stock_report_with_purchased_rate(request):
    product_batches = ProductBatches.objects.all()

    total_amount = 0
    for item in product_batches:
        item.total_amount = item.quantity * item.purchased_price
        total_amount += item.total_amount

    context = {
        'product_batches': product_batches,
        'total_amount': total_amount
    }

    return render(request, 'inventory/stock_report_with_purchased_rate.html', context)


from django.utils import timezone
from datetime import timedelta

def stock_report_with_sale_rate(request):
    # Get all product batches with related product data
    product_batches = ProductBatches.objects.select_related('product').all()
    
    today = timezone.now().date()
    total_amount = 0
    total_commission_amount = 0
    total_quantity = 0
    near_expiry_count = 0
    expired_count = 0
    
    for item in product_batches:
        # Calculate financials
        item.total_amount = item.quantity * item.product.unit_price
        total_amount += item.total_amount
        item.total_commission_amount = item.quantity * item.product.commission
        total_commission_amount += item.total_commission_amount
        total_quantity += item.quantity
        
        # Calculate expiry status
        days_to_expiry = (item.expiry_date - today).days
        item.days_to_expiry = days_to_expiry
        
        if days_to_expiry <= 0:
            item.expiry_status = 'Expired'
            expired_count += 1
        elif days_to_expiry <= 30:  # Near expiry if <= 30 days
            item.expiry_status = 'Near Expiry'
            near_expiry_count += 1
        else:
            item.expiry_status = 'OK'
        
        # Calculate potential profit (assuming you have cost price)
        if hasattr(item.product, 'cost_price'):
            item.profit_per_unit = item.product.unit_price - item.product.cost_price
            item.total_profit = item.quantity * item.profit_per_unit
        
        # Add product name for easier access in template
        item.product_name = item.product.title
        
        # Calculate stock value at purchased price
        item.purchased_value = item.quantity * item.purchased_price

    context = {
        'product_batches': product_batches,
        'total_amount': total_amount,
        'total_commission_amount': total_commission_amount,
        'total_quantity': total_quantity,
        'near_expiry_count': near_expiry_count,
        'expired_count': expired_count,
        'report_date': today,
    }
    
    return render(request, 'inventory/stock_report_with_sale_rate.html', context)


def received_order_information(request):
    received_order_items = ReceivedOrderItem.objects.all()

    total_amount = 0  # Initialize total_amount

    # Calculate the total amount for each item and update the total_amount
    for item in received_order_items:
        item.total_amount = item.received_quantity * item.get_GP_Price()
        total_amount += item.total_amount

    context = {
        'received_order_items': received_order_items,
        'total_amount': total_amount,
    }

    return render(request, 'inventory/received_order_information.html', context)


def sale_report(request):
    sale_order_items = SaleOrderItems.objects.all()

    # Calculate the grand total of all sale order items
    grand_total = sum(item.total_amount for item in sale_order_items)

    context = {
        'sale_order_items': sale_order_items,
        'grand_total': grand_total,
    }

    return render(request, 'inventory/sale_report.html', context)


def share_holder_details(request, pk):
    # Fetch the specific ShareHolder
    share_holder = ShareHolders.objects.get(id=pk)

    # Fetch ShareHolderDeposit records and generate summaries
    deposits = ShareHolderDeposit.objects.filter(share_holder=share_holder)
    if not deposits.exists():
        context = {
            'share_holder': share_holder,
            'deposits': deposits
        }
        return render(request, 'inventory/share_holder_no_balance.html', context)

    currency_summaries = deposits.values('currency__code').annotate(
        total_in=Sum('amount_in'), total_out=Sum('amount_out')
    )

    total_currency_summaries = {}  # To store the total summary for each currency

    for currency_summary in currency_summaries:
        currency_code = currency_summary['currency__code']
        balance = currency_summary['total_in'] - currency_summary['total_out']
        currency_summary['balance'] = balance

        if currency_code in total_currency_summaries:
            total_currency_summaries[currency_code]['total_in'] += currency_summary['total_in']
            total_currency_summaries[currency_code]['total_out'] += currency_summary['total_out']
            total_currency_summaries[currency_code]['total_balance'] += balance
        else:
            total_currency_summaries[currency_code] = {
                'total_in': currency_summary['total_in'],
                'total_out': currency_summary['total_out'],
                'total_balance': balance,
            }

    context = {
        'share_holder': share_holder,
        'currency_summaries': currency_summaries,
        'total_currency_summaries': total_currency_summaries,
        'deposits': deposits,
    }

    return render(request, 'inventory/share_holder_details.html', context)


@login_required
def consumer_outstanding_details(request):
    if request.method == 'POST':
        start_date = request.POST.get('start_date')
        end_date = request.POST.get('end_date')
        selected_consumer = request.POST.get('doctor')
        current_date = date.today()
        consumer = Consumer.objects.all()
        consumer_balance = ConsumerAccount.objects.filter().order_by('tx_date')

        if start_date and end_date:
            try:
                start_date = parse(start_date).date()
                end_date = parse(end_date).date()
            except ValueError:
                start_date = None
                end_date = None
            else:
                consumer_balance = consumer_balance.filter(
                    tx_date__range=(start_date, end_date))

        if selected_consumer:
            # Create a filter condition for the selected consumer
            consumer_filter = Q(consumer=selected_consumer)

            # Apply the consumer filter to the queryset
            consumer_balance = consumer_balance.filter(consumer_filter)

        # Check if there are any records within the selected date range
        records_exist = consumer_balance.exists()

        if records_exist:
            # Order the queryset
            consumer_balance = consumer_balance.order_by('tx_date')

            # Aggregate the debit, credit, and current_balance fields for the queryset
            totals = consumer_balance.aggregate(
                total_debit=Sum('debit'),
                total_credit=Sum('credit'),
                total_balance=Sum('current_balance')
            )
            from_date = consumer_balance.first().tx_date
            to_date = consumer_balance.last().tx_date
            net_balance = totals['total_credit'] - totals['total_debit']

            # Get the currently logged-in user's name
            current_user_name = request.user.get_full_name()

            context = {
                'current_user_name': current_user_name,
                'current_date': current_date,
                'consumer': consumer,
                'from_date': from_date,
                'to_date': to_date,
                'totals': totals,
                'balance': consumer_balance,
                'total_debit': totals['total_debit'],
                'total_credit': totals['total_credit'],
                'net_balance': net_balance,
                'total_balance': totals['total_balance'],
            }
        else:

            context = {
                'start_date': start_date,
                'end_date': end_date,

            }
            return render(request, 'inventory/no_outstanding_details_alert.html', context)
        return render(request, 'inventory/print_consumer_outstanding_details.html', context)

    consumer = Consumer.objects.all()
    context = {
        'consumer': consumer
    }
    return render(request, 'inventory/parameter_consumer_outstanding_details.html', context)


@login_required
def commission_details(request):
    if request.method == 'POST':
        start_date = request.POST.get('start_date')
        end_date = request.POST.get('end_date')
        selected_doctor = request.POST.get('doctor')
        current_date = date.today()
        doctor = Doctor.objects.all()
        doctor_balance = DoctorCommission.objects.filter().order_by('placed_at')

        if start_date and end_date:
            try:
                start_date = parse(start_date).date()
                end_date = parse(end_date).date()
            except ValueError:
                start_date = None
                end_date = None
            else:
                doctor_balance = doctor_balance.filter(
                    placed_at__range=(start_date, end_date))

        if selected_doctor:
            # Create a filter condition for the selected doctor
            doctor_filter = Q(doctor_id=selected_doctor)

            # Apply the doctor filter to the queryset
            doctor_balance = doctor_balance.filter(doctor_filter)

        # Check if there are any records within the selected date range
        records_exist = doctor_balance.exists()

        if records_exist:
            # Order the queryset
            doctor_balance = doctor_balance.order_by('placed_at')

            # Aggregate the debit, credit, and current_balance fields for the queryset
            totals = doctor_balance.aggregate(
                total_debit=Sum('debit'),
                total_credit=Sum('credit'),
                total_balance=Sum('current_balance')
            )
            from_date = doctor_balance.first().placed_at
            to_date = doctor_balance.last().placed_at
            net_balance = totals['total_credit'] - totals['total_debit']

            # Get the currently logged-in user's name
            current_user_name = request.user.get_full_name()

            context = {
                'current_user_name': current_user_name,
                'current_date': current_date,
                'doctor': doctor,
                'from_date': from_date,
                'to_date': to_date,
                'totals': totals,
                'balance': doctor_balance,
                'total_debit': totals['total_debit'],
                'total_credit': totals['total_credit'],
                'net_balance': net_balance,
                'total_balance': totals['total_balance'],
            }
        else:

            context = {
                'start_date': start_date,
                'end_date': end_date,

            }
            return render(request, 'inventory/no_commission_details_report.html', context)
        return render(request, 'inventory/print_commission_details.html', context)

    doctor = Doctor.objects.all()
    context = {
        'doctor': doctor
    }
    return render(request, 'inventory/parameter_commission_details.html', context)


from datetime import datetime, timedelta
from collections import defaultdict
from decimal import Decimal
from django.shortcuts import render
from django.db.models import Sum, F, ExpressionWrapper, DecimalField
from django.utils import timezone

from inventory.models import (
    SaleOrder, SaleOrderItems,
    Consumer, ConsumerReturnItem,
    Product
)


def SalesReport(request):
    payment_status = request.GET.get('payment_status')
    consumer_id = request.GET.get('consumer')
    product_id = request.GET.get('product')
    doctor_id = request.GET.get('doctor')  # New doctor filter

    today = timezone.now().date()
    default_start_date = today - timedelta(days=30)
    default_end_date = today

    start_date_str = request.GET.get('start_date')
    end_date_str = request.GET.get('end_date')

    try:
        start_date = datetime.strptime(start_date_str, '%Y-%m-%d') if start_date_str else datetime.combine(default_start_date, datetime.min.time())
        end_date = datetime.strptime(end_date_str, '%Y-%m-%d') if end_date_str else datetime.combine(default_end_date, datetime.max.time())
    except ValueError:
        start_date = datetime.combine(default_start_date, datetime.min.time())
        end_date = datetime.combine(default_end_date, datetime.max.time())

    end_date = end_date.replace(hour=23, minute=59, second=59, microsecond=999999)

    saleorders = SaleOrder.objects.select_related('consumer', 'visited_by').filter(
        created_at__range=(start_date, end_date)
    )

    if payment_status:
        saleorders = saleorders.filter(payment_status=payment_status)
    if consumer_id:
        saleorders = saleorders.filter(consumer_id=consumer_id)
    if product_id:
        saleorders = saleorders.filter(saleorderitems__product__product_id=product_id)
    if doctor_id:
        saleorders = saleorders.filter(saleorderitems__doctor_id=doctor_id)  # New doctor filter

    sales_data = []
    total_revenue = Decimal(0)
    total_items_sold = Decimal(0)

    for order in saleorders.distinct():
        sold_items = order.saleorderitems_set.all()
        sold_total = sum(item.total_amount for item in sold_items)
        sold_quantity = sum(item.quantity for item in sold_items)

        return_items = ConsumerReturnItem.objects.filter(saleorder_item__saleorder=order)
        return_total = sum(item.return_amount for item in return_items)
        return_quantity = sum(item.quantity for item in return_items)

        net_revenue = sold_total - return_total
        total_revenue += net_revenue
        total_items_sold += sold_quantity - return_quantity

        sales_data.append({
            'order': order,
            'sold_total': sold_total,
            'return_total': return_total,
            'net_revenue': net_revenue,
            'item_count': sold_items.count(),
        })

    sold_products = SaleOrderItems.objects.filter(
        saleorder__in=saleorders
    ).values('product__product__title', 'product__batch_number').annotate(
        sold_qty=Sum('quantity'),
        sold_revenue=Sum(ExpressionWrapper(F('quantity') * F('net_price'), output_field=DecimalField()))
    )

    returned_products = ConsumerReturnItem.objects.filter(
        saleorder_item__saleorder__in=saleorders
    ).values('saleorder_item__product__product__title', 'saleorder_item__product__batch_number').annotate(
        return_qty=Sum('quantity'),
        return_revenue=Sum(ExpressionWrapper(F('quantity') * F('saleorder_item__net_price'), output_field=DecimalField()))
    )

    return_map = {
        (r['saleorder_item__product__product__title'], r['saleorder_item__product__batch_number']): {
            'return_qty': r['return_qty'] or Decimal(0),
            'return_revenue': r['return_revenue'] or Decimal(0)
        }
        for r in returned_products
    }

    top_products = []
    for p in sold_products:
        title = p['product__product__title']
        batch_number = p['product__batch_number']
        sold_qty = p['sold_qty'] or Decimal(0)
        sold_revenue = p['sold_revenue'] or Decimal(0)

        ret_key = (title, batch_number)
        return_qty = return_map.get(ret_key, {}).get('return_qty', Decimal(0))
        return_revenue = return_map.get(ret_key, {}).get('return_revenue', Decimal(0))

        net_qty = sold_qty - return_qty
        net_revenue = sold_revenue - return_revenue

        top_products.append({
            'title': title,
            'batch_number': batch_number,
            'sold_qty': sold_qty,
            'return_qty': return_qty,
            'net_qty': net_qty,
            'net_revenue': net_revenue
        })

    top_products.sort(key=lambda x: x['net_qty'], reverse=True)

    context = {
        'sale_data': sales_data,
        'total_revenue': total_revenue,
        'total_sales': saleorders.distinct().count(),
        'total_items': total_items_sold,
        'start_date': start_date.strftime('%Y-%m-%d'),
        'end_date': end_date.strftime('%Y-%m-%d'),
        'payment_status': payment_status or '',
        'selected_consumer': consumer_id or '',
        'selected_product': product_id or '',
        'selected_doctor': doctor_id or '',  # for template form default value
        'consumers': Consumer.objects.all(),
        'products': Product.objects.all(),
        'doctors': Doctor.objects.all(),  # add doctor list to context
        'top_products': top_products,
    }

    return render(request, 'inventory/sales_report.html', context)

from django.shortcuts import render
from django.db.models import Sum, F, DecimalField
from .models import ConsumerAccount, Consumer

def consumer_account_summary_report(request):
    start_date = request.GET.get('start_date')
    end_date = request.GET.get('end_date')
    selected_consumer_id = request.GET.get('consumer')

    qs = ConsumerAccount.objects.select_related('consumer').all()

    if start_date:
        qs = qs.filter(tx_date__gte=start_date)
    if end_date:
        qs = qs.filter(tx_date__lte=end_date)

    # Summary by consumer
    summary = (
        qs.values('consumer__id', 'consumer__name')
        .annotate(
            total_credit=Sum('credit'),
            total_debit=Sum('debit'),
            balance=Sum(F('credit') - F('debit'), output_field=DecimalField())
        )
        .order_by('consumer__name')
    )

    # Grand total
    grand_total = qs.aggregate(
        total_credit=Sum('credit') or 0,
        total_debit=Sum('debit') or 0,
        balance=Sum(F('credit') - F('debit'), output_field=DecimalField()) or 0
    )

    consumers = Consumer.objects.all()

    # If a consumer is selected, get their detailed records
    selected_consumer = None
    consumer_details = []
    consumer_totals = None
    if selected_consumer_id:
        selected_consumer = Consumer.objects.filter(id=selected_consumer_id).first()
        consumer_details = qs.filter(consumer_id=selected_consumer_id).order_by('-tx_date', '-id')

        # Totals for selected consumer
        consumer_totals = consumer_details.aggregate(
            total_credit=Sum('credit'),
            total_debit=Sum('debit'),
            balance=Sum(F('credit') - F('debit'), output_field=DecimalField())
        )

    context = {
        'summary': summary,
        'grand_total': grand_total,
        'consumers': consumers,
        'selected_consumer_id': int(selected_consumer_id) if selected_consumer_id else None,
        'selected_consumer': selected_consumer,
        'consumer_details': consumer_details,
        'consumer_totals': consumer_totals,
        'start_date': start_date,
        'end_date': end_date,
    }
    return render(request, 'reports/consumer_account_summary.html', context)
