from rest_framework.decorators import api_view, permission_classes
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from account.permissions import (
    HasTabPermission,
    user_can_see_all_shop_data,
    get_user_data_filter,
)
from rest_framework import status
from decimal import Decimal, InvalidOperation
from django.db.models import Sum, Q
from django.utils import timezone
from datetime import datetime, timedelta
from collections import defaultdict

from shop_management.models.data.customer import Customer
from shop_management.models.data.sale import Sale
from shop_management.models.data.sale_payment import SalePayment
from shop_management.models.data.sale_return import SaleReturn
from shop_management.models.data.customer_balance_payment import CustomerBalancePayment
from shop_management.models.data.customer_debtor import CustomerDebtor
from shop_management.serializers.data.customer import CustomerSerializer
from shop_management.serializers.data.sale import SaleSerializer
from shop_management.serializers.data.sale_payment import SalePaymentSerializer
from shop_management.serializers.data.sale_return import SaleReturnSerializer
from shop_management.serializers.data.customer_balance_payment import CustomerBalancePaymentSerializer
from shop_management.serializers.data.customer_debtor import CustomerDebtorSerializer


def _sum_amount(val):
    """Sum amount from serializer: can be dict (currency -> amount) or number."""
    if val is None:
        return 0.0
    if isinstance(val, (int, float, Decimal)):
        return float(val)
    if isinstance(val, dict):
        return sum(float(v) for v in val.values())
    if isinstance(val, str):
        try:
            return float(Decimal(val))
        except (InvalidOperation, ValueError):
            return 0.0
    return 0.0


def _sum_return_amount(return_data):
    total = _sum_amount(return_data.get("total_refound_amount"))
    if total:
        return total

    return_items = return_data.get("return_items") or []
    items_total = 0.0
    for item in return_items:
        items_total += _sum_amount(
            item.get("refund_amount") or item.get("calculated_refund_amount")
        )
    return items_total


def _get_payment_customer_id(payment_data, sale_to_customer):
    sale_details = payment_data.get("sale_details") or {}
    sale_id = payment_data.get("sale") or sale_details.get("id")
    return sale_to_customer.get(sale_id) if sale_id is not None else None


def _get_return_customer_id(return_data, sale_to_customer):
    sale_details = return_data.get("sale_details") or {}
    customer_details = sale_details.get("customer_details") or {}
    customer_id = customer_details.get("id")
    if customer_id is not None:
        return customer_id

    sale_id = return_data.get("sale") or sale_details.get("id")
    return sale_to_customer.get(sale_id) if sale_id is not None else None


@api_view(["GET"])
@permission_classes([IsAuthenticated, HasTabPermission])
def customer_report(request):
    """
    GET ?start_date=YYYY-MM-DD&end_date=YYYY-MM-DD (optional)
    Returns customers with their sales, payments, returns, and balance payments.
    """
    try:
        start_date = request.GET.get("start_date")
        end_date = request.GET.get("end_date")
        shop = request.GET.get("shop")
        customer_id = request.GET.get("customer")

        if user_can_see_all_shop_data(request.user, "reports"):
            customers_qs = Customer.objects.all()
            sales_qs = Sale.objects.all()
            payments_qs = SalePayment.objects.all()
            returns_qs = SaleReturn.objects.all()
            balance_payments_qs = CustomerBalancePayment.objects.filter(is_void=False)
            debtors_qs = CustomerDebtor.objects.all()
        else:
            customers_qs = Customer.objects.filter(get_user_data_filter(request.user, "shop")).distinct()
            sales_qs = Sale.objects.filter(get_user_data_filter(request.user, "shop")).distinct()
            payments_qs = SalePayment.objects.filter(get_user_data_filter(request.user, "sale__shop")).distinct()
            returns_qs = SaleReturn.objects.filter(get_user_data_filter(request.user, "shop")).distinct()
            balance_payments_qs = CustomerBalancePayment.objects.filter(is_void=False).filter(
                get_user_data_filter(request.user, "customer__shop")
            ).distinct()
            debtors_qs = CustomerDebtor.objects.filter(
                get_user_data_filter(request.user, "customer__shop")
            ).distinct()

        if start_date:
            start_d = datetime.strptime(start_date, "%Y-%m-%d").date()
            sales_qs = sales_qs.filter(sale_date__gte=start_d)
            payments_qs = payments_qs.filter(payment_date__date__gte=start_d)
            returns_qs = returns_qs.filter(return_date__gte=start_d)
            start_dt = timezone.make_aware(
                datetime.combine(start_d, datetime.min.time()),
                timezone.get_current_timezone(),
            )
            balance_payments_qs = balance_payments_qs.filter(
                payment_date__gte=start_dt
            )
            debtors_qs = debtors_qs.filter(loan_date__gte=start_dt)
        if end_date:
            end_d = datetime.strptime(end_date, "%Y-%m-%d").date()
            sales_qs = sales_qs.filter(sale_date__lte=end_d)
            payments_qs = payments_qs.filter(payment_date__date__lte=end_d)
            returns_qs = returns_qs.filter(return_date__lte=end_d)
            end_dt = timezone.make_aware(
                datetime.combine(end_d + timedelta(days=1), datetime.min.time()),
                timezone.get_current_timezone(),
            )
            balance_payments_qs = balance_payments_qs.filter(
                payment_date__lt=end_dt
            )
            debtors_qs = debtors_qs.filter(loan_date__lt=end_dt)

        if shop:
            customers_qs = customers_qs.filter(shop_id=shop)
            sales_qs = sales_qs.filter(shop_id=shop)
            payments_qs = payments_qs.filter(sale__shop_id=shop)
            returns_qs = returns_qs.filter(shop_id=shop)
            balance_payments_qs = balance_payments_qs.filter(customer__shop_id=shop)
            debtors_qs = debtors_qs.filter(customer__shop_id=shop)

        if customer_id:
            customers_qs = customers_qs.filter(id=customer_id)
            sales_qs = sales_qs.filter(customer_id=customer_id)
            payments_qs = payments_qs.filter(sale__customer_id=customer_id)
            returns_qs = returns_qs.filter(sale__customer_id=customer_id)
            balance_payments_qs = balance_payments_qs.filter(customer_id=customer_id)
            debtors_qs = debtors_qs.filter(customer_id=customer_id)

        # Draft new-engine sales are operational worksheets only. Keep legacy
        # records compatible, but never expose a draft as customer activity.
        sales_qs = sales_qs.filter(
            Q(uses_inventory_engine=False) | Q(document_status=Sale.DocumentStatus.POSTED)
        )
        payments_qs = payments_qs.filter(
            Q(sale__uses_inventory_engine=False)
            | Q(sale__document_status=Sale.DocumentStatus.POSTED)
        )

        customers = customers_qs.select_related("shop").order_by("name")
        customers_data = CustomerSerializer(customers, many=True).data

        # Per-customer: sales, payments, returns, balance_payments
        sales = (
            sales_qs.select_related("customer", "currency", "shop")
            .prefetch_related(
                "sale_items__product",
                "sale_items__unit",
                "sale_items__purchase_item",
                "sale_items__purchase_item__purchase",
                "sale_items__purchase_item__purchase__supplier",
            )
            .order_by("-sale_date")
        )
        payments = payments_qs.select_related("sale", "sale__customer").order_by(
            "-payment_date"
        )
        returns = returns_qs.select_related("sale", "sale__customer").order_by(
            "-return_date"
        )
        balance_payments = balance_payments_qs.select_related(
            "customer", "currency"
        ).order_by("-payment_date")
        debtors = debtors_qs.select_related(
            "customer", "currency", "account", "account__currency"
        ).order_by("-loan_date")

        sales_data = SaleSerializer(sales, many=True).data
        payments_data = SalePaymentSerializer(payments, many=True).data
        returns_data = SaleReturnSerializer(returns, many=True).data
        balance_payments_data = CustomerBalancePaymentSerializer(
            balance_payments, many=True
        ).data
        debtors_data = CustomerDebtorSerializer(debtors, many=True).data

        # sale_id -> customer_id for grouping payments and returns,
        # even when the payment/return date is filtered independently of sale_date.
        sale_to_customer = {}
        for sale in Sale.objects.filter(id__in={p.sale_id for p in payments if p.sale_id} | {r.sale_id for r in returns if r.sale_id} | {s.id for s in sales if s.id}).select_related("customer"):
            if sale.id is not None and sale.customer_id is not None:
                sale_to_customer[sale.id] = sale.customer_id

        # Build per-customer summary
        by_customer = defaultdict(
            lambda: {
                "sales": [],
                "payments": [],
                "returns": [],
                "balance_payments": [],
                "debtors": [],
                "total_sales_amount": 0.0,
                "total_payments_amount": 0.0,
                "total_paid_from_sales_amount": 0.0,
                "total_returns_amount": 0.0,
                "total_balance_payments_amount": 0.0,
                "total_debtors_amount": 0.0,
                "total_debtors_outstanding_amount": 0.0,
            }
        )

        payment_totals_by_customer = defaultdict(float)
        for payment in payments:
            if payment.sale_id and payment.sale and payment.sale.customer_id:
                payment_totals_by_customer[payment.sale.customer_id] += float(
                    payment.amount or 0
                )

        for s in sales_data:
            cid = s.get("customer")
            if cid is not None:
                by_customer[cid]["sales"].append(s)
                by_customer[cid]["total_sales_amount"] += _sum_amount(
                    s.get("effective_total_amount") or s.get("total_amount")
                )
                by_customer[cid]["total_paid_from_sales_amount"] += _sum_amount(
                    s.get("total_paid")
                )

        for p in payments_data:
            cid = _get_payment_customer_id(p, sale_to_customer)
            if cid is not None:
                by_customer[cid]["payments"].append(p)

        for r in returns_data:
            cid = _get_return_customer_id(r, sale_to_customer)
            if cid is not None:
                by_customer[cid]["returns"].append(r)
                by_customer[cid]["total_returns_amount"] += _sum_return_amount(r)

        for bp in balance_payments_data:
            cid = bp.get("customer")
            if cid is not None:
                by_customer[cid]["balance_payments"].append(bp)
                by_customer[cid]["total_balance_payments_amount"] += _sum_amount(
                    bp.get("amount")
                )

        for d in debtors_data:
            cid = d.get("customer")
            if cid is not None:
                by_customer[cid]["debtors"].append(d)
                by_customer[cid]["total_debtors_amount"] += _sum_amount(d.get("amount"))
                by_customer[cid]["total_debtors_outstanding_amount"] += _sum_amount(
                    d.get("outstanding_amount")
                )

        # Attach summary to each customer in list
        result_customers = []
        for c in customers_data:
            cid = c.get("id")
            summary = by_customer.get(
                cid,
                {
                    "sales": [],
                    "payments": [],
                    "returns": [],
                    "balance_payments": [],
                    "debtors": [],
                    "total_sales_amount": 0.0,
                    "total_payments_amount": 0.0,
                    "total_paid_from_sales_amount": 0.0,
                    "total_returns_amount": 0.0,
                    "total_balance_payments_amount": 0.0,
                    "total_debtors_amount": 0.0,
                    "total_debtors_outstanding_amount": 0.0,
                },
            )
            paid_payments_amount = payment_totals_by_customer.get(
                cid, summary["total_payments_amount"]
            )
            if paid_payments_amount == 0 and summary["total_paid_from_sales_amount"] > 0:
                paid_payments_amount = summary["total_paid_from_sales_amount"]
            net_sales = (
                summary["total_sales_amount"]
                - summary["total_returns_amount"]
            )
            outstanding = (
                summary["total_sales_amount"]
                - paid_payments_amount
                - summary["total_returns_amount"]
                + summary["total_debtors_outstanding_amount"]
            )
            result_customers.append(
                {
                    "customer": c,
                    "sales": summary["sales"],
                    "payments": summary["payments"],
                    "returns": summary["returns"],
                    "balance_payments": summary["balance_payments"],
                    "debtors": summary["debtors"],
                    "summary": {
                        "total_sales_amount": summary["total_sales_amount"],
                        "total_payments_amount": paid_payments_amount,
                        "total_paid_payments_amount": (
                            paid_payments_amount
                            + summary["total_balance_payments_amount"]
                        ),
                        "total_paid_from_sales_amount": summary[
                            "total_paid_from_sales_amount"
                        ],
                        "total_returns_amount": summary["total_returns_amount"],
                        "total_balance_payments_amount": summary[
                            "total_balance_payments_amount"
                        ],
                        "total_debtors_amount": summary["total_debtors_amount"],
                        "total_debtors_outstanding_amount": summary[
                            "total_debtors_outstanding_amount"
                        ],
                        "net_sales_amount": net_sales,
                        "outstanding_amount": outstanding,
                        "original_opening_by_currency": c.get("original_opening_balances_by_currency", {}),
                        "opening_paid_by_currency": c.get("paid_opening_balances_by_currency", {}),
                        "remaining_opening_by_currency": c.get("opening_balances_by_currency", {}),
                        "total_owed_by_currency": c.get("total_owed_by_currency", {}),
                        "credit_by_currency": c.get("credit_by_currency", {}),
                    },
                }
            )

        return Response(
            {"customers": result_customers},
            status=status.HTTP_200_OK,
        )
    except Exception as e:
        return Response(
            {"error": str(e)},
            status=status.HTTP_500_INTERNAL_SERVER_ERROR,
        )


customer_report.required_tab = "reports"
