from collections import defaultdict
from decimal import Decimal

from django.db.models import Q
from django.utils import timezone
from django.utils.dateparse import parse_date
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from account.permissions import (
    HasTabPermission,
    accessible_shops,
    admin_branch_context_active,
    assert_shop_access,
)
from shop_management.models.data.account_transaction import AccountTransactionType
from shop_management.models.data.customer import Customer
from shop_management.models.data.expense import Expense
from shop_management.models.data.inventory import StockBalance
from shop_management.models.data.notification import OperationalAlert
from shop_management.models.data.purchase import Purchase
from shop_management.models.data.sale import Sale
from shop_management.models.data.shop import Shop
from shop_management.models.data.shop_account import Account
from shop_management.models.data.supplier import Supplier
from shop_management.services.notifications import sync_operational_alerts


def _add(target, currency, amount):
    target[currency or "Unknown"] += Decimal(str(amount or 0))


def _amounts(values):
    return [{"currency": key, "amount": str(value.quantize(Decimal("0.01")))} for key, value in sorted(values.items())]


def _invoice_sales(queryset):
    totals = defaultdict(Decimal)
    for sale in queryset.prefetch_related("sale_items").select_related("currency"):
        currency = sale.currency.title if sale.currency else "Unknown"
        total = sum((Decimal(str(item.quantity)) * Decimal(str(item.sale_price)) for item in sale.sale_items.all()), Decimal("0"))
        total -= Decimal(str(sale.discount_amount or sale.amount or 0))
        _add(totals, currency, max(total, Decimal("0")))
    return totals


def _invoice_purchases(queryset):
    totals = defaultdict(Decimal)
    for purchase in queryset.prefetch_related("purchase_items__currency").select_related("currency", "supplier"):
        for currency, amount in (purchase.total_price or {}).items():
            _add(totals, currency, amount)
    return totals


class DashboardSummaryView(APIView):
    permission_classes = [IsAuthenticated, HasTabPermission]
    required_tab = "dashboard"

    def get(self, request):
        # The selected-branch header scopes normal users. A super administrator
        # must still be able to compare every branch from this overview.
        shops = (
            Shop.objects.all()
            if request.user.is_superuser and not admin_branch_context_active(request)
            else accessible_shops(request.user, request)
        )
        requested_shop = request.query_params.get("shop")
        if requested_shop:
            selected = (
                Shop.objects.filter(pk=requested_shop).first()
                if request.user.is_superuser
                else assert_shop_access(request.user, requested_shop, request)
            )
            if selected is None:
                from rest_framework.exceptions import NotFound
                raise NotFound("Branch not found.")
            shops = shops.filter(pk=selected.pk)
        shop_ids = list(shops.values_list("pk", flat=True))
        today = timezone.localdate()
        start = parse_date(request.query_params.get("start_date", ""))
        end = parse_date(request.query_params.get("end_date", ""))
        valid_documents = Q(uses_inventory_engine=False) | Q(document_status="POSTED")
        sales = Sale.objects.filter(shop_id__in=shop_ids).filter(valid_documents)
        purchases = Purchase.objects.filter(shop_id__in=shop_ids, is_deleted=False).filter(valid_documents)
        period_sales, period_purchases = sales, purchases
        if start:
            period_sales = period_sales.filter(sale_date__gte=start)
            period_purchases = period_purchases.filter(purchase_date__gte=start)
        if end:
            period_sales = period_sales.filter(sale_date__lte=end)
            period_purchases = period_purchases.filter(purchase_date__lte=end)

        receivables, payables = defaultdict(Decimal), defaultdict(Decimal)
        for customer in Customer.objects.filter(shop_id__in=shop_ids):
            for currency, amount in customer.get_total_owed_by_currency().items():
                _add(receivables, currency, amount)
        for supplier in Supplier.objects.filter(shop_id__in=shop_ids, is_deleted=False):
            for currency, amount in supplier.get_total_owed_by_currency().items():
                _add(payables, currency, amount)

        account_balances = []
        for account in Account.objects.filter(
            shop_id__in=shop_ids, is_active=True, is_system_account=False
        ).select_related("currency", "shop").prefetch_related("transactions"):
            balance = Decimal(str(account.opening_balance or 0))
            for transaction in account.transactions.all():
                amount = Decimal(str(transaction.converted_amount or 0))
                balance += amount if transaction.transaction_type == AccountTransactionType.CREDIT else -amount
            account_balances.append({"id": account.pk, "name": account.name, "branch": account.shop.name, "account_type": account.account_type, "currency": account.currency.title, "balance": str(balance.quantize(Decimal("0.01")))})

        alerts = sync_operational_alerts(request.user, request).filter(shop_id__in=shop_ids)
        alert_counts = {key: alerts.filter(alert_type=key).count() for key, _ in OperationalAlert.Type.choices}

        products = defaultdict(lambda: {"quantity": Decimal("0"), "revenue": defaultdict(Decimal)})
        for sale in period_sales.prefetch_related("sale_items__product").select_related("currency"):
            currency = sale.currency.title if sale.currency else "Unknown"
            for item in sale.sale_items.all():
                if not item.product:
                    continue
                row = products[(item.product_id, item.product.name)]
                row["quantity"] += Decimal(str(item.quantity or 0))
                row["revenue"][currency] += Decimal(str(item.quantity or 0)) * Decimal(str(item.sale_price or 0))
        top_products = [
            {"product_id": key[0], "product": key[1], "quantity": str(value["quantity"]), "revenue": _amounts(value["revenue"])}
            for key, value in sorted(products.items(), key=lambda row: row[1]["quantity"], reverse=True)[:5]
        ]

        branch_comparison = []
        for shop in shops:
            shop_receivables, shop_payables = defaultdict(Decimal), defaultdict(Decimal)
            for customer in Customer.objects.filter(shop=shop):
                for currency, amount in customer.get_total_owed_by_currency().items():
                    _add(shop_receivables, currency, amount)
            for supplier in Supplier.objects.filter(shop=shop, is_deleted=False):
                for currency, amount in supplier.get_total_owed_by_currency().items():
                    _add(shop_payables, currency, amount)

            shop_accounts = defaultdict(Decimal)
            for account in Account.objects.filter(
                shop=shop, is_active=True, is_system_account=False
            ).select_related("currency").prefetch_related("transactions"):
                balance = Decimal(str(account.opening_balance or 0))
                for transaction in account.transactions.all():
                    amount = Decimal(str(transaction.converted_amount or 0))
                    balance += amount if transaction.transaction_type == AccountTransactionType.CREDIT else -amount
                _add(shop_accounts, account.currency.title, balance)

            stock_units = Decimal("0")
            stock_value = defaultdict(Decimal)
            stock_balances = StockBalance.objects.filter(warehouse__shop=shop, quantity__gt=0).select_related("cost_currency")
            for stock in stock_balances:
                quantity = Decimal(str(stock.quantity or 0))
                stock_units += quantity
                _add(stock_value, stock.cost_currency.title, quantity * Decimal(str(stock.average_cost or 0)))

            branch_comparison.append({
                "branch_id": shop.pk, "branch": shop.name,
                "sales": _amounts(_invoice_sales(period_sales.filter(shop=shop))),
                "purchases": _amounts(_invoice_purchases(period_purchases.filter(shop=shop))),
                "receivables": _amounts(shop_receivables),
                "payables": _amounts(shop_payables),
                "account_balances": _amounts(shop_accounts),
                "stock_units": str(stock_units.quantize(Decimal("0.0001"))),
                "stock_value": _amounts(stock_value),
                "active_alerts": alerts.filter(shop=shop).count(),
            })

        expense_groups = defaultdict(lambda: defaultdict(Decimal))
        expenses = Expense.objects.filter(shop_id__in=shop_ids, status="POSTED").select_related("category", "currency")
        if start:
            expenses = expenses.filter(payment_date__date__gte=start)
        if end:
            expenses = expenses.filter(payment_date__date__lte=end)
        for expense in expenses:
            expense_groups[expense.category.name if expense.category else "Uncategorized"][expense.currency.title if expense.currency else "Unknown"] += Decimal(str(expense.amount or 0))

        return Response({
            "as_of": timezone.now(),
            "today_sales": _amounts(_invoice_sales(sales.filter(sale_date=today))),
            "today_purchases": _amounts(_invoice_purchases(purchases.filter(purchase_date=today))),
            "period_sales": _amounts(_invoice_sales(period_sales)),
            "period_purchases": _amounts(_invoice_purchases(period_purchases)),
            "receivables": _amounts(receivables), "payables": _amounts(payables),
            "account_balances": account_balances, "alert_counts": alert_counts,
            "top_products": top_products, "branch_comparison": branch_comparison,
            "expense_breakdown": [{"category": category, "amounts": _amounts(amounts)} for category, amounts in sorted(expense_groups.items())],
        })
