# sales/views/barcode.py
import logging
import json
import csv
import string
from datetime import datetime, timedelta
from decimal import Decimal

from django.shortcuts import render, redirect, get_object_or_404
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import JsonResponse, HttpResponse
from django.views.decorators.csrf import csrf_exempt
from django.utils import timezone
from django.db import transaction
from django.db.models import Sum, Count, Q, F
from django.core.exceptions import PermissionDenied

from inventory.models import Product
from core.models import Branch
from core.utils import check_limit_or_block, filter_orders_by_user_branch
from accounts.decorators import role_required
from accounts.audit import log_action
from companies.permissions import company_feature_required
from sales.models import (
    POSOrder, POSOrderItem, PaymentTransaction,
    DailySalesSummary, UnusualTransaction, Customer,
)
from sales.forms import POSOrderForm, POSOrderItemForm

logger = logging.getLogger(__name__)


@login_required
@company_feature_required('feature_barcode_pos')
def barcode_pos(request):
    """Dedicated barcode scanner POS page — subscription-gated."""
    company = getattr(request, 'company', None)
    
    company_settings = getattr(company, 'settings', None)
    unlimited_stock = getattr(company_settings, 'enable_unlimited_stock', False) if company_settings else False

    # Ensure active session order
    order = None
    order_id = request.session.get('barcode_order_id')
    if order_id:
        try:
            order = POSOrder.objects.get(id=order_id, company=company, status='pending')
        except POSOrder.DoesNotExist:
            order = None

    if order is None:
        user_branch = getattr(request.user, 'branch', None)
        if user_branch is None and company:
            user_branch = Branch.objects.filter(company=company, is_active=True).first()
        elif user_branch is None:
            user_branch = Branch.objects.filter(is_active=True).first()
        order = POSOrder.objects.create(
            company=company,
            order_number=f"BORD-{timezone.now().strftime('%m%d%H%M')}-{''.join(__import__('random').choices(__import__('string').ascii_uppercase+__import__('string').digits,k=5))}",
            total_amount=0, tax_amount=0, final_amount=0,
            cashier=request.user.username,
            payment_method='cash',
            branch=user_branch,
        )
        request.session['barcode_order_id'] = order.id

    if request.method == 'POST':
        action = request.POST.get('action', '').strip()

        if action == 'complete_order':
            from core.utils import check_limit_or_block
            from django.core.exceptions import PermissionDenied
            from sales.suspicious import detect_suspicious
            from core.email_utils import send_credit_customer_alert
            import uuid as _uuid_bpos

            try:
                check_limit_or_block('orders_per_day', request=request)
            except PermissionDenied as e:
                messages.error(request, str(e))
                return redirect('sales:barcode_pos')

            is_split = request.POST.get('is_split_payment') == '1'
            payment_method  = request.POST.get('payment_method', 'cash')
            customer_name   = request.POST.get('customer_name', '')
            customer_phone  = request.POST.get('customer_phone', '')
            amount_paid_str = request.POST.get('amount_paid', '')

            order_items = POSOrderItem.objects.filter(order=order)

            # Guard: don't complete an empty order
            if not order_items.exists():
                messages.warning(request, 'Cart is empty — add products before completing the order.')
                return redirect('sales:barcode_pos')

            # Resolve payment method (credit is a status, not a method)
            if payment_method == 'credit':
                payment_method = 'cash'

            subtotal = Decimal('0')
            for item in order_items:
                subtotal += item.total_price

            # VAT from company settings
            vat_rate = Decimal(str(getattr(company_settings, 'vat_rate', 0) or 0))
            tax_amount = (subtotal * vat_rate / 100).quantize(Decimal('0.01'))

            # Promo / discount
            promo_code_v = request.POST.get('promo_code', '').strip().upper()
            discount_raw = request.POST.get('discount_amount', '0')
            try:
                discount_amt = Decimal(str(discount_raw))
                if discount_amt > subtotal:
                    discount_amt = subtotal
                if discount_amt < 0:
                    discount_amt = Decimal('0')
            except Exception:
                discount_amt = Decimal('0')

            final_amt = subtotal + tax_amount - discount_amt

            order.total_amount    = subtotal
            order.tax_amount      = tax_amount
            order.discount_amount = discount_amt
            order.final_amount    = final_amt
            order.customer_name   = customer_name
            order.customer_phone  = customer_phone

            if is_split:
                split_method_1 = request.POST.get('split_method_1', 'cash')
                split_method_2 = request.POST.get('split_method_2', 'pos')
                try:
                    split_amount_1 = Decimal(str(request.POST.get('split_amount_1', '0')))
                except Exception:
                    split_amount_1 = Decimal('0')
                try:
                    split_amount_2 = Decimal(str(request.POST.get('split_amount_2', '0')))
                except Exception:
                    split_amount_2 = Decimal('0')

                order.payment_method  = 'split'
                order.is_split_payment = True
                order.split_method_1  = split_method_1
                order.split_method_2  = split_method_2
                order.amount_paid     = final_amt
                order.balance_amount  = Decimal('0')
                order.payment_status  = 'full'
                order.status = 'completed'
                order.save()

                if split_amount_1 > 0:
                    PaymentTransaction.objects.create(
                        order=order, payment_method=split_method_1,
                        amount=split_amount_1,
                        reference_number=f"BSPLIT1-{_uuid_bpos.uuid4().hex[:8].upper()}",
                        status='completed',
                    )
                if split_amount_2 > 0:
                    PaymentTransaction.objects.create(
                        order=order, payment_method=split_method_2,
                        amount=split_amount_2,
                        reference_number=f"BSPLIT2-{_uuid_bpos.uuid4().hex[:8].upper()}",
                        status='completed',
                    )
            else:
                order.payment_method  = payment_method
                order.is_split_payment = False
                try:
                    amount_paid = Decimal(str(amount_paid_str)) if amount_paid_str else final_amt
                except Exception:
                    amount_paid = final_amt

                order.amount_paid   = amount_paid
                order.balance_amount = max(final_amt - amount_paid, Decimal('0'))

                if order.balance_amount > 0:
                    order.payment_status = 'partial' if amount_paid > 0 else 'credit'
                else:
                    order.payment_status = 'full'

                order.status = 'completed'
                order.save()

                if amount_paid > 0:
                    PaymentTransaction.objects.create(
                        order=order, payment_method=payment_method,
                        amount=amount_paid,
                        reference_number=f"BPOS-{_uuid_bpos.uuid4().hex[:10].upper()}",
                        status='completed',
                    )

            if 'barcode_order_id' in request.session:
                del request.session['barcode_order_id']

            log_action(request, 'complete_order', target=f'Barcode Order #{order.order_number}')
            detect_suspicious(order)

            if not is_split and order.balance_amount > 0 and customer_name:
                send_credit_customer_alert(customer_name, customer_phone, order.order_number, order.balance_amount)

            # ── Generate Daily Summary ──
            from sales.models import DailySalesSummary
            DailySalesSummary.generate_summary_from_queryset(
                timezone.now().date(),
                POSOrder.objects.filter(company=request.company, created_at__date=timezone.now().date(), status='completed')
            )

            messages.success(request, f'Order #{order.order_number} completed!')
            if request.POST.get('skip_receipt'):
                return redirect('sales:barcode_pos')
            return redirect('sales:print_receipt', order_id=order.id)


        elif action == 'clear_order':
            for item in order.items.all():
                if not unlimited_stock:
                    item.product.stock_quantity += item.quantity
                    item.product.save()
            order.delete()
            if 'barcode_order_id' in request.session:
                del request.session['barcode_order_id']
            messages.info(request, 'Barcode order cleared.')
            return redirect('sales:barcode_pos')

        elif action == 'update_qty':
            from decimal import Decimal as _D
            item_id = request.POST.get('item_id')
            delta   = int(request.POST.get('delta', 0))
            try:
                item = POSOrderItem.objects.select_related('product').get(id=item_id, order=order)
            except POSOrderItem.DoesNotExist:
                messages.error(request, 'Item not found.')
                return redirect('sales:barcode_pos')
            new_qty = item.quantity + delta
            if new_qty <= 0:
                # Remove from order and restore stock
                if not unlimited_stock:
                    item.product.stock_quantity += item.quantity
                    item.product.save()
                item.delete()
            else:
                if delta > 0 and not unlimited_stock and item.product.stock_quantity < delta:
                    messages.error(request, f'Only {item.product.stock_quantity} unit(s) left in stock.')
                    return redirect('sales:barcode_pos')
                # Resolve price (wholesale if qty meets threshold)
                ws_price   = float(item.product.wholesale_price or 0)
                ws_min_qty = int(item.product.wholesale_min_quantity or 0)
                unit_price = ws_price if (ws_price > 0 and ws_min_qty > 0 and new_qty >= ws_min_qty) else float(item.unit_price)
                if not unlimited_stock:
                    if delta > 0:
                        item.product.stock_quantity -= delta
                    elif delta < 0:
                        item.product.stock_quantity += abs(delta)
                    item.product.save()
                item.quantity    = new_qty
                item.unit_price  = Decimal(str(unit_price))
                item.total_price = Decimal(str(unit_price)) * new_qty
                item.save()
            return redirect('sales:barcode_pos')

        elif action == 'remove_item':
            item_id = request.POST.get('item_id')
            try:
                item = POSOrderItem.objects.select_related('product').get(id=item_id, order=order)
                if not unlimited_stock:
                    item.product.stock_quantity += item.quantity
                    item.product.save()
                item.delete()
            except POSOrderItem.DoesNotExist:
                pass
            return redirect('sales:barcode_pos')

    order_items = POSOrderItem.objects.filter(order=order).select_related('product')
    subtotal = sum(item.total_price for item in order_items)

    vat_rate = Decimal(str(getattr(company_settings, 'vat_rate', 0) or 0))
    tax_amount = (subtotal * vat_rate / 100).quantize(Decimal('0.01'))

    # Preserve existing discount if any (usually starts at 0)
    current_discount = order.discount_amount or Decimal('0')

    order.total_amount = subtotal
    order.tax_amount   = tax_amount
    order.final_amount = max(subtotal + tax_amount - current_discount, Decimal('0'))
    order.save()

    customers_qs = Customer.objects.order_by('name')
    if company:
        customers_qs = customers_qs.filter(company=company)
    return render(request, 'sales/barcode_pos.html', {
        'order': order,
        'order_items': order_items,
        'subtotal': subtotal,
        'tax_amount': tax_amount,
        'vat_rate': vat_rate,
        'final_total': order.final_amount,
        'company_settings': company_settings,
        'unlimited_stock': unlimited_stock,
        'user_role': request.user.role,
        'user_name': request.user.get_full_name() or request.user.username,
        'customers': customers_qs,
    })


@login_required
def api_scan_barcode(request):
    """
    AJAX: GET ?barcode=<code> → product/variant JSON.
    Looks up ProductVariant first (by barcode or SKU), then falls back to Product.
    Deducts from VariantBranchStock → BranchStock → product global stock (in that priority).
    """
    barcode = request.GET.get('barcode', '').strip()
    if not barcode:
        return JsonResponse({'ok': False, 'message': 'No barcode provided'}, status=400)

    from inventory.models import ProductVariant, VariantBranchStock, BranchStock

    company = request.company
    _settings = getattr(company, 'settings', None)
    _unlimited = getattr(_settings, 'enable_unlimited_stock', False) if _settings else False
    user_branch = getattr(request.user, 'branch', None)

    # ── 1. Try variant first, then base product ──────────────────────────────
    variant = ProductVariant.objects.filter(
        company=company,
        is_active=True,
        product__is_hidden=False,
    ).filter(Q(barcode=barcode) | Q(sku=barcode)).select_related('product').first()

    if variant:
        product = variant.product
        unit_price = variant.effective_price
        item_label = f"{product.name} ({variant.display_label})"

        if not _unlimited:
            if user_branch:
                vbs, _ = VariantBranchStock.objects.get_or_create(
                    company=company, variant=variant, branch=user_branch, defaults={'quantity': 0}
                )
                avail = vbs.quantity
            else:
                avail = variant.stock_quantity

            if avail < 1:
                return JsonResponse({'ok': False, 'message': f'"{item_label}" is out of stock'}, status=400)
    else:
        product = Product.objects.filter(
            company=company, status='active', is_hidden=False,
        ).filter(Q(barcode=barcode) | Q(sku=barcode)).first()

        if not product:
            return JsonResponse({'ok': False, 'message': f'No product found for barcode: {barcode}'}, status=404)

        variant = None
        unit_price = product.price
        item_label = product.name

        if not _unlimited:
            if user_branch:
                bs, _ = BranchStock.objects.get_or_create(
                    company=company, product=product, branch=user_branch, defaults={'quantity': 0}
                )
                avail = bs.quantity
            else:
                avail = product.stock_quantity

            if avail < 1:
                return JsonResponse({'ok': False, 'message': f'"{item_label}" is out of stock'}, status=400)

    # ── 2. Add to session order ──────────────────────────────────────────────
    order_id = request.session.get('barcode_order_id')
    item_id = None

    if order_id:
        try:
            order = POSOrder.objects.get(id=order_id, company=company, status='pending')

            # Match existing line for same product + variant combo
            qs = POSOrderItem.objects.filter(order=order, product=product)
            if variant:
                qs = qs.filter(variant_id=variant.id)
            else:
                qs = qs.filter(variant_id__isnull=True)
            existing = qs.first()

            if existing:
                new_qty = existing.quantity + 1
                ws_price   = product.wholesale_price
                ws_min_qty = product.wholesale_min_quantity
                final_price = ws_price if (ws_price and ws_min_qty and ws_min_qty > 0 and new_qty >= ws_min_qty) else unit_price
                existing.quantity    = new_qty
                existing.unit_price  = final_price
                existing.total_price = final_price * new_qty
                existing.save()
                item_id = existing.id
            else:
                new_item = POSOrderItem.objects.create(
                    order=order, product=product,
                    quantity=1, unit_price=unit_price, total_price=unit_price,
                    variant_id=variant.id if variant else None,
                )
                item_id = new_item.id

            # ── 3. Deduct stock ──────────────────────────────────────────────
            if not _unlimited:
                if variant:
                    if user_branch:
                        vbs, _ = VariantBranchStock.objects.get_or_create(
                            company=company, variant=variant, branch=user_branch, defaults={'quantity': 0}
                        )
                        vbs.quantity = max(0, vbs.quantity - 1)
                        vbs.save()  # triggers product.stock_quantity sync
                    else:
                        variant.stock_quantity = max(0, variant.stock_quantity - 1)
                        variant.save()
                else:
                    if user_branch:
                        bs, _ = BranchStock.objects.get_or_create(
                            company=company, product=product, branch=user_branch, defaults={'quantity': 0}
                        )
                        bs.quantity = max(0, bs.quantity - 1)
                        bs.save()  # triggers product.stock_quantity sync
                    else:
                        product.stock_quantity = max(0, product.stock_quantity - 1)
                        product.save()

            # ── 4. Recalc order totals ───────────────────────────────────────
            items = POSOrderItem.objects.filter(order=order)
            raw_subtotal = sum(float(i.total_price) for i in items)
            _vat_rate = float(getattr(_settings, 'vat_rate', 0) or 0)
            _tax = round(raw_subtotal * _vat_rate / 100, 2)
            order.total_amount = raw_subtotal
            order.tax_amount   = _tax
            order.final_amount = raw_subtotal + _tax
            order.save()

        except POSOrder.DoesNotExist:
            item_id = None

    # determine stock remaining to return to frontend
    if not _unlimited:
        if variant:
            stock_remaining = (vbs.quantity if user_branch else variant.stock_quantity) if 'vbs' in dir() else variant.stock_quantity
        else:
            stock_remaining = (bs.quantity if user_branch else product.stock_quantity) if 'bs' in dir() else product.stock_quantity
    else:
        stock_remaining = 9999

    return JsonResponse({
        'ok': True,
        'product': {
            'id': product.id,
            'name': item_label,
            'sku': variant.sku if variant else product.sku,
            'barcode': barcode,
            'price': float(unit_price),
            'stock_quantity': stock_remaining,
            'item_id': item_id,
            'is_variant': variant is not None,
            'variant_id': variant.id if variant else None,
        }
    })


# ─────────────────────────────────────────────────────────────
# END-OF-DAY CASH RECONCILIATION  (#21)
# ─────────────────────────────────────────────────────────────




