
import logging
import csv

logger = logging.getLogger(__name__)
from decimal import Decimal
from django.core.paginator import Paginator
from django.forms import modelformset_factory
from django.shortcuts import render, get_object_or_404, redirect
from django.http import JsonResponse, HttpResponse
from django.contrib.auth.decorators import login_required
from django.db import transaction
from django.utils.timezone import timedelta
from datetime import datetime
from django.views.decorators.http import require_http_methods
from django.db.models import Sum, Count, Q, F, DecimalField
from django.db.models.functions import TruncMonth, Coalesce
from .forms import SaleForm, SaleItemForm
from .models import Sale, SaleItem
from inventory.models import Product
from customers.models import Customer
from settings.models import Shop  # Assuming shop details are in the settings app
from django.db.models import Q
import json
from django.views.decorators.csrf import csrf_exempt
from django.utils import timezone
from django.contrib import messages
from django.utils.timezone import localtime
import pytz


logger = logging.getLogger('sales')


@login_required(login_url='/auth/login/')
def dashboard(request):
    try:
        today = timezone.now().date()
        start_of_week = today - timedelta(days=today.weekday())
        start_of_month = today.replace(day=1)
        six_months_ago = today - timedelta(days=6*30)
        two_weeks_ago = today - timedelta(days=14)
    except Exception as e:
        logger.error(f"Error in dashboard date calculations: {e}")
        # Fallback to a basic dashboard
        return render(request, 'sales/sale_dashboard.html', {
            'daily_sales': {},
            'weekly_sales': {},
            'monthly_sales': {},
            'error_message': 'Unable to load dashboard data. Please try again later.'
        })

    # Daily sales with transaction count
    try:
        daily_sales = Sale.objects.filter(sale_date__date=today).aggregate(
            total_sales=Sum('total_amount'),
            cash_sales=Sum('total_amount', filter=Q(payment_method='cash')),
            mpesa_sales=Sum('total_amount', filter=Q(payment_method='mpesa')),
            credit_sales=Sum('total_amount', filter=Q(payment_method='credit')),
            cheque_sales=Sum('total_amount', filter=Q(payment_method='cheque')),
            transaction_count=Count('id')
        )
    except Exception as e:
        logger.error(f"Error calculating daily sales: {e}")
        daily_sales = {
            'total_sales': 0, 'cash_sales': 0, 'mpesa_sales': 0,
            'credit_sales': 0, 'cheque_sales': 0, 'transaction_count': 0
        }

    # Weekly sales with growth comparison
    weekly_sales = Sale.objects.filter(sale_date__date__gte=start_of_week).aggregate(
        total_sales=Sum('total_amount'),
        cash_sales=Sum('total_amount', filter=Q(payment_method='cash')),
        mpesa_sales=Sum('total_amount', filter=Q(payment_method='mpesa')),
        credit_sales=Sum('total_amount', filter=Q(payment_method='credit')),
        cheque_sales=Sum('total_amount', filter=Q(payment_method='cheque'))
    )

    # Previous week for growth calculation
    prev_week_start = start_of_week - timedelta(days=7)
    prev_week_sales = Sale.objects.filter(
        sale_date__date__gte=prev_week_start,
        sale_date__date__lt=start_of_week
    ).aggregate(total=Sum('total_amount'))['total'] or 0

    # Calculate weekly growth
    weekly_growth = 0
    if prev_week_sales > 0:
        weekly_growth = ((weekly_sales['total_sales'] or 0) - prev_week_sales) / prev_week_sales * 100

    # Monthly sales
    monthly_sales = Sale.objects.filter(sale_date__date__gte=start_of_month).aggregate(
        total_sales=Sum('total_amount'),
        cash_sales=Sum('total_amount', filter=Q(payment_method='cash')),
        mpesa_sales=Sum('total_amount', filter=Q(payment_method='mpesa')),
        credit_sales=Sum('total_amount', filter=Q(payment_method='credit')),
        cheque_sales=Sum('total_amount', filter=Q(payment_method='cheque'))
    )

    # Six months sales for chart
    six_months_sales = Sale.objects.filter(
        sale_date__date__gte=six_months_ago
    ).annotate(
        month=TruncMonth('sale_date')
    ).values('month').annotate(
        total_sales=Sum('total_amount'),
        cash_sales=Sum('total_amount', filter=Q(payment_method='cash')),
        mpesa_sales=Sum('total_amount', filter=Q(payment_method='mpesa')),
        credit_sales=Sum('total_amount', filter=Q(payment_method='credit')),
        cheque_sales=Sum('total_amount', filter=Q(payment_method='cheque'))
    ).order_by('month')

    # Outstanding credits and debt analysis
    credit_sales_with_balance = Sale.objects.filter(
        payment_method='credit'
    ).annotate(
        outstanding=F('total_amount') - Coalesce(F('credit_amount'), Decimal('0'), output_field=DecimalField())
    ).filter(outstanding__gt=0)

    total_outstanding_credit = credit_sales_with_balance.aggregate(
        total=Sum('outstanding')
    )['total'] or 0

    customers_with_debt = credit_sales_with_balance.values('customer').distinct().count()

    # Overdue debts (over 2 weeks old)
    overdue_debts = Sale.objects.filter(
        payment_method='credit',
        sale_date__date__lt=two_weeks_ago
    ).annotate(
        outstanding_amount=F('total_amount') - Coalesce(F('credit_amount'), Decimal('0'), output_field=DecimalField())
    ).filter(outstanding_amount__gt=0).select_related('customer').order_by('-outstanding_amount')

    total_overdue_amount = overdue_debts.aggregate(
        total=Sum('outstanding_amount')
    )['total'] or 0

    # Recent sales (last 10)
    recent_sales = Sale.objects.select_related('customer').order_by('-sale_date')[:10]

    # Cheque statistics
    cheque_stats = {
        'total_cheques': Sale.objects.filter(payment_method='cheque').count(),
        'cleared_cheques': Sale.objects.filter(payment_method='cheque', cheque_cleared=True).count(),
        'pending_cheques': Sale.objects.filter(payment_method='cheque', cheque_cleared=False).count(),
        'pending_amount': Sale.objects.filter(
            payment_method='cheque',
            cheque_cleared=False
        ).aggregate(Sum('total_amount'))['total_amount__sum'] or 0
    }

    # Monthly target calculation (example: 100k per month)
    monthly_target = 100000  # You can make this configurable
    monthly_target_percentage = 0
    if monthly_target > 0:
        monthly_target_percentage = ((monthly_sales['total_sales'] or 0) / monthly_target) * 100

    context = {
        'daily_sales': daily_sales,
        'weekly_sales': weekly_sales,
        'monthly_sales': monthly_sales,
        'six_months_sales': list(six_months_sales),
        'cheque_stats': cheque_stats,
        'daily_transactions': daily_sales['transaction_count'] or 0,
        'weekly_growth': round(weekly_growth, 1),
        'monthly_target_percentage': round(monthly_target_percentage, 1),
        'total_outstanding_credit': total_outstanding_credit,
        'customers_with_debt': customers_with_debt,
        'overdue_debts': overdue_debts[:5],  # Show only top 5 in alert
        'total_overdue_amount': total_overdue_amount,
        'recent_sales': recent_sales,
    }

    return render(request, 'sales/sale_dashboard.html', context)


@login_required(login_url='/auth/login/')
def sales_list(request):
    sales = Sale.objects.select_related('customer', 'user').order_by('-sale_date')
    
    # Search functionality
    search_query = request.GET.get('search', '').strip()
    if search_query:
        sales = sales.filter(
            Q(id__icontains=search_query) |
            Q(customer__first_name__icontains=search_query) |
            Q(customer__last_name__icontains=search_query) |
            Q(customer__phone__icontains=search_query) |
            Q(mpesa_reference__icontains=search_query) |
            Q(cheque_number__icontains=search_query) |
            Q(user__username__icontains=search_query)
        )
    
    # Filter by payment method
    payment_method = request.GET.get('payment_method', '')
    if payment_method:
        sales = sales.filter(payment_method=payment_method)
    
    # Filter by date range
    date_from = request.GET.get('date_from', '')
    date_to = request.GET.get('date_to', '')
    
    if date_from:
        try:
            from_date = datetime.strptime(date_from, '%Y-%m-%d').date()
            sales = sales.filter(sale_date__date__gte=from_date)
        except ValueError:
            pass
    
    if date_to:
        try:
            to_date = datetime.strptime(date_to, '%Y-%m-%d').date()
            sales = sales.filter(sale_date__date__lte=to_date)
        except ValueError:
            pass
    
    # Handle CSV export
    if request.GET.get('export') == 'csv':
        response = HttpResponse(content_type='text/csv')
        response['Content-Disposition'] = 'attachment; filename="sales_list.csv"'
        
        writer = csv.writer(response)
        writer.writerow([
            'Sale ID', 'Date', 'Customer', 'Amount', 'Payment Method', 
            'Status', 'Served By', 'Mpesa Reference', 'Cheque Number'
        ])
        
        for sale in sales:
            customer_name = f"{sale.customer.first_name} {sale.customer.last_name}" if sale.customer else "Walk-in"
            writer.writerow([
                sale.id,
                sale.sale_date.strftime('%Y-%m-%d %H:%M'),
                customer_name,
                sale.total_amount,
                sale.get_payment_method_display(),
                'Complete' if sale.is_complete else 'Pending',
                sale.user.username,
                sale.mpesa_reference or '',
                sale.cheque_number or ''
            ])
        
        return response
    
    # Pagination
    paginator = Paginator(sales, 25)  # Show 25 sales per page
    page_number = request.GET.get('page')
    page_obj = paginator.get_page(page_number)

    context = {
        'sales': page_obj,
    }
    return render(request, 'sales/sales_list.html', context)


@login_required(login_url='/auth/login/')
def sale_detail(request, sale_id):
    sale = get_object_or_404(Sale, id=sale_id)
    sale_items = SaleItem.objects.filter(sale=sale)

    context = {
        'sale': sale,
        'sale_items': sale_items,
    }
    return render(request, 'sales/sale_detail.html', context)


@csrf_exempt
@transaction.atomic
@login_required(login_url='/auth/login/')
@require_http_methods(["GET", "POST"])
def make_sale(request):
    if request.method == 'GET':
        return render(request, 'sales/make_sale.html', {
            'shop': Shop.objects.first(),
            'user': request.user
        })

    try:
        data = json.loads(request.body)

        # Live Search Handlers
        if 'query' in data:
            return handle_live_search(data)

        # Main Sale Processing
        return process_sale(request, data)

    except json.JSONDecodeError:
        return JsonResponse({'status': 'error', 'message': 'Invalid JSON'}, status=400)
    except Exception as e:
        return JsonResponse({'status': 'error', 'message': str(e)}, status=500)


def handle_live_search(data):
    query = data['query'].strip()
    if data.get('type') == 'customer':
        customers = Customer.objects.filter(
            Q(name__icontains=query) | Q(phone__icontains=query)
        )[:10]  # <- Closing parenthesis was missing here
        return JsonResponse({'customers': [
            {'id': c.id, 'name': c.name, 'phone': c.phone}
            for c in customers
        ]})
    elif data.get('type') == 'product':
        products = Product.objects.filter(
            Q(name__icontains=query) | Q(barcode__icontains=query),
            stock_quantity__gt=0
        )
        return JsonResponse({'products': [
            {'id': p.id, 'name': p.name,
             'price': str(p.selling_price),
             'stock': p.stock_quantity}
            for p in products[:10]
        ]})
    return JsonResponse({'status': 'error', 'message': 'Invalid search type'}, status=400)


def process_sale(request, data):
    # Validate required fields
    if not (items := data.get('items')):
        return JsonResponse({'status': 'error', 'message': 'No sale items provided'}, status=400)

    payment_method = data.get('payment_method')
    if payment_method not in dict(Sale.PAYMENT_METHOD_CHOICES):
        return JsonResponse({'status': 'error', 'message': 'Invalid payment method'}, status=400)

    # Process customer
    customer = None
    if customer_id := data.get('customer_id'):
        try:
            customer = Customer.objects.get(id=customer_id)
        except Customer.DoesNotExist:
            return JsonResponse({'status': 'error', 'message': 'Customer not found'}, status=404)

    # Payment method specific validation
    validation_error = validate_payment_method(data, payment_method, customer)
    if validation_error:
        return validation_error

    # Create sale record
    sale = create_sale_record(request, data, customer, payment_method)

    # Process sale items with stock validation
    try:
        process_sale_items(sale, items)
    except ValueError as e:
        return JsonResponse({'status': 'error', 'message': str(e)}, status=400)

    # Update customer credit if applicable
    if payment_method == 'credit' and customer:
        customer.update_credit_balance(sale.credit_amount)

    return JsonResponse({
        'status': 'success',
        'sale_id': sale.id,
        'total_amount': str(sale.total_amount),
        'change_due': str(sale.change_due or 0),
        'balance_due': str(sale.outstanding_credit)
    })


def validate_payment_method(data, payment_method, customer):
    if payment_method == 'cash':
        if Decimal(data.get('cash_given', 0)) < Decimal(data['total_amount']):
            return JsonResponse({
                'status': 'error',
                'message': 'Insufficient cash provided'
            }, status=400)

    elif payment_method == 'mpesa':
        if not data.get('mpesa_reference'):
            return JsonResponse({
                'status': 'error',
                'message': 'M-Pesa reference required'
            }, status=400)

    elif payment_method == 'cheque':
        if not data.get('cheque_number') or not data.get('cheque_date'):
            return JsonResponse({
                'status': 'error',
                'message': 'Cheque number and date required'
            }, status=400)
        try:
            datetime.strptime(data['cheque_date'], '%Y-%m-%d')
        except ValueError:
            return JsonResponse({
                'status': 'error',
                'message': 'Invalid cheque date format (YYYY-MM-DD required)'
            }, status=400)

    elif payment_method == 'credit':
        if not customer:
            return JsonResponse({
                'status': 'error',
                'message': 'Customer required for credit sales'
            }, status=400)
        if not customer.can_purchase_on_credit(Decimal(data['total_amount'])):
            return JsonResponse({
                'status': 'error',
                'message': 'Customer credit limit exceeded'
            }, status=400)

    return None


def create_sale_record(request, data, customer, payment_method):
    total_amount = sum(
        Decimal(item['price']) * int(item['quantity'])
        for item in data['items']
    )

    sale_data = {
        'customer': customer,
        'total_amount': total_amount,
        'payment_method': payment_method,
        'user': request.user,
        'is_complete': payment_method != 'credit'
    }

    # Payment method specific fields
    if payment_method == 'cash':
        sale_data.update({
            'cash_given': Decimal(data['cash_given']),
            'change_due': Decimal(data['cash_given']) - total_amount
        })
    elif payment_method == 'mpesa':
        sale_data['mpesa_reference'] = data['mpesa_reference']
    elif payment_method == 'cheque':
        sale_data.update({
            'cheque_number': data['cheque_number'],
            'cheque_date': data['cheque_date']
        })
    elif payment_method == 'credit':
        sale_data['credit_amount'] = Decimal(data.get('credit_paid', 0))

    return Sale.objects.create(**sale_data)


def process_sale_items(sale, items):
    for item in items:
        product = Product.objects.select_for_update().get(id=item['product_id'])
        quantity = int(item['quantity'])

        if product.stock_quantity < quantity:
            raise ValueError(f'Insufficient stock for {product.name}')

        sale_item = SaleItem(
            sale=sale,
            product=product,
            quantity=quantity,
            selling_price=Decimal(item['price']),
            subtotal=Decimal(item['price']) * quantity
        )
        # Skip the model's save method to avoid overriding the selling_price
        super(SaleItem, sale_item).save()
        
        # Manually update stock
        product.stock_quantity -= quantity
        product.save()


def get_receipt_data(sale):
    nairobi_tz = pytz.timezone('Africa/Nairobi')
    return {
        'sale_date': localtime(sale.sale_date, nairobi_tz),
        # Other context variables
    }


@login_required(login_url='/auth/login/')
def receipt_view(request, sale_id):
    sale = get_object_or_404(Sale, id=sale_id)
    sale_items = sale.items.all()

    # CORRECTED CALCULATIONS
    if sale.payment_method == 'credit':
        credit_paid = sale.credit_amount  # What customer ACTUALLY paid now
        balance_due = sale.total_amount - sale.credit_amount  # What's left
    else:
        credit_paid = None
        balance_due = 0

    context = {
        'shop': Shop.objects.first(),
        'sale': sale,
        'items': sale_items,
        'total_amount': sale.total_amount,
        'payment_method': sale.payment_method,
        'credit_paid': credit_paid,  # Deposit amount (e.g., 250)
        'balance_due': balance_due,   # Outstanding balance (e.g., 500)
        'cash_given': sale.cash_given,
        'change_due': sale.change_due,
        'mpesa_reference': sale.mpesa_reference,
        'served_by': sale.user.username,
        'sale_date': sale.sale_date.strftime('%Y-%m-%d %H:%M:%S'),
        'customer_name': f"{sale.customer.first_name} {sale.customer.last_name}" if sale.customer else "N/A",
        'customer_phone': sale.customer.phone if sale.customer else "N/A",
        'cheque_number': sale.cheque_number if sale.payment_method == 'cheque' else None,
    }
    return render(request, 'sales/receipt.html', context)


@login_required(login_url='/auth/login/')
def search_products(request):
    query = request.GET.get('q', '').strip().lower()
    products = Product.objects.filter(
        Q(name__icontains=query) | Q(barcode__icontains=query)
    ).values('id', 'name', 'barcode', 'selling_price', 'stock_quantity')
    return JsonResponse(list(products), safe=False)


@login_required(login_url='/auth/login/')
def search_customers(request):
    query = request.GET.get('q', '').strip().lower()
    customers = Customer.objects.filter(
        Q(first_name__icontains=query) | Q(last_name__icontains=query) | Q(phone__icontains=query)
    ).values('id', 'first_name', 'last_name', 'phone')
    return JsonResponse(list(customers), safe=False)


@login_required(login_url='/auth/login/')
@transaction.atomic
def update_sale(request, sale_id):
    sale = get_object_or_404(Sale, id=sale_id)

    # Create a formset for SaleItems
    SaleItemFormSet = modelformset_factory(SaleItem, form=SaleItemForm, extra=0, can_delete=True)

    if request.method == 'POST':
        if request.headers.get('x-requested-with') == 'XMLHttpRequest':  # Handle AJAX requests
            try:
                data = json.loads(request.body)
                sale.total_amount = Decimal(data.get('total_amount', sale.total_amount))
                sale.payment_method = data.get('payment_method', sale.payment_method)

                customer_id = data.get('customer_id')
                if customer_id:
                    sale.customer = get_object_or_404(Customer, id=customer_id)

                sale.save()

                return JsonResponse({'status': 'success', 'message': 'Sale updated successfully!'})

            except Exception as e:
                return JsonResponse({'status': 'error', 'message': str(e)}, status=400)

        else:  # Handle full-page form submission
            sale_form = SaleForm(request.POST, instance=sale)
            sale_item_formset = SaleItemFormSet(request.POST, queryset=SaleItem.objects.filter(sale=sale))

            if sale_form.is_valid() and sale_item_formset.is_valid():
                sale_form.save()

                sale_items = sale_item_formset.save(commit=False)
                for item in sale_items:
                    original_item = SaleItem.objects.get(id=item.id)
                    stock_adjustment = original_item.quantity - item.quantity
                    item.batch.product.stock_quantity += stock_adjustment
                    item.batch.product.save()
                    item.save()

                return redirect('sales_list')

            else:
                return JsonResponse({'status': 'error', 'message': 'Invalid form data'})

    else:  # Handle GET request for editing
        if request.headers.get('x-requested-with') == 'XMLHttpRequest':  # Handle AJAX modal edit
            data = {
                'total_amount': str(sale.total_amount),
                'payment_method': sale.payment_method,
                'customer_id': sale.customer.id if sale.customer else None
            }
            return JsonResponse(data)

        else:  # Handle full-page edit form
            sale_form = SaleForm(instance=sale)
            sale_item_formset = SaleItemFormSet(queryset=SaleItem.objects.filter(sale=sale))

            context = {
                'sale': sale,
                'sale_form': sale_form,
                'sale_items': sale_item_formset,
            }
            return render(request, 'sales/update_sale.html', context)


@login_required(login_url='/auth/login/')
@transaction.atomic
def delete_sale(request, sale_id):
    sale = get_object_or_404(Sale, id=sale_id)

    if request.method == 'POST':
        try:
            # Return sold items back to stock before deleting the sale
            for item in sale.items.all():
                product = item.product
                product.stock_quantity += item.quantity  # Add the sold quantity back to the product's stock
                product.save()
                logger.info(f"Restored {item.quantity} units of {product.name} to stock")

            # Delete the sale and its items
            sale_number = sale.id
            sale.delete()
            logger.info(f"Sale #{sale_number} deleted successfully")

            if request.headers.get('x-requested-with') == 'XMLHttpRequest':  # Check for AJAX request
                return JsonResponse({'success': True, 'message': f'Sale #{sale_number} deleted successfully'})
            else:
                messages.success(request, 'Sale deleted successfully and stock quantities restored.')
                return redirect('sales_list')

        except Exception as e:
            logger.error(f"Error deleting sale #{sale_id}: {str(e)}")
            if request.headers.get('x-requested-with') == 'XMLHttpRequest':
                return JsonResponse({'success': False, 'error': str(e)}, status=500)
            else:
                messages.error(request, f'Error deleting sale: {str(e)}')
                return redirect('sales_list')

    return JsonResponse({'error': 'Invalid request method.'}, status=405)
