from collections import defaultdict
from datetime import date, timedelta
from decimal import Decimal

from django.db.models import Sum
from rest_framework.exceptions import ValidationError
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from account.permissions import HasTabPermission, get_user_shop_filter
from shop_management.models.data.account_transaction import AccountTransaction
from shop_management.models.data.account_transfer import AccountTransfer
from shop_management.models.data.customer_payment import CustomerPayment
from shop_management.models.data.shop import Shop
from shop_management.models.data.shop_account import Account
from shop_management.models.data.supplier_payment import SupplierPayment


def money(value):
    return str(Decimal(value or 0).quantize(Decimal("0.01")))


class FinanceReportView(APIView):
    permission_classes = [IsAuthenticated, HasTabPermission]
    required_tab = "reports"

    def get(self, request):
        try:
            end = date.fromisoformat(request.query_params.get("end_date") or str(date.today()))
            start = date.fromisoformat(request.query_params.get("start_date") or str(end - timedelta(days=29)))
        except ValueError:
            raise ValidationError({"date": "Use YYYY-MM-DD format."})
        if start > end:
            raise ValidationError({"end_date": "End date cannot be before start date."})
        try:
            shop = Shop.objects.get(get_user_shop_filter(request.user), pk=request.query_params.get("shop"))
        except (Shop.DoesNotExist, ValueError, TypeError):
            raise ValidationError({"shop": "Branch was not found or is not accessible."})
        account_id = request.query_params.get("account")
        accounts = Account.objects.filter(shop=shop, is_system_account=False).select_related("currency").order_by("account_type", "name")
        if account_id:
            accounts = accounts.filter(pk=account_id)
            if not accounts.exists():
                raise ValidationError({"account": "Account was not found in this branch."})

        account_rows = []
        totals = defaultdict(Decimal)
        for account in accounts:
            through_end = account.transactions.filter(transaction_date__lte=end)
            credits = through_end.filter(transaction_type="credit").aggregate(value=Sum("converted_amount"))["value"] or Decimal("0")
            debits = through_end.filter(transaction_type="debit").aggregate(value=Sum("converted_amount"))["value"] or Decimal("0")
            balance = account.opening_balance + credits - debits
            totals[account.currency.title] += balance
            account_rows.append({"account_id": account.id, "account": account.name, "type": account.get_account_type_display(),
                                 "institution": account.bank_name or account.sarafi_name, "currency": account.currency.title,
                                 "opening_balance": money(account.opening_balance), "credits": money(credits),
                                 "debits": money(debits), "balance": money(balance), "is_active": account.is_active})

        transactions = AccountTransaction.objects.filter(
            account__shop=shop, account__in=accounts, transaction_date__range=(start, end),
        ).select_related("account", "account_currency", "transaction_currency")[:2000]
        transaction_rows = [{"date": row.transaction_date, "account": row.account.name,
                             "account_type": row.account.get_account_type_display(), "direction": row.get_transaction_type_display(),
                             "amount": money(row.amount), "transaction_currency": row.transaction_currency.title,
                             "rate": str(row.exchange_rate), "account_amount": money(row.converted_amount),
                             "account_currency": row.account_currency.title, "description": row.description or ""}
                            for row in transactions]
        transfers = AccountTransfer.objects.filter(
            from_account__shop=shop, uses_controlled_engine=True, transfer_date__range=(start, end),
        ).exclude(status=AccountTransfer.Status.DRAFT).select_related("from_account", "to_account", "from_currency", "to_currency")
        transfer_rows = [{"date": row.transfer_date, "number": row.transfer_number, "from_account": row.from_account.name,
                          "to_account": row.to_account.name, "sent": money(row.amount), "from_currency": row.from_currency.title,
                          "received": money(row.converted_amount), "to_currency": row.to_currency.title,
                          "rate": str(row.exchange_rate), "service_charge": money(row.service_charge), "status": row.status}
                         for row in transfers]
        receipts = CustomerPayment.objects.filter(shop=shop, status=CustomerPayment.Status.POSTED, payment_date__range=(start, end)).select_related("customer", "financial_account", "currency")
        receipt_rows = [{"date": row.payment_date, "customer": row.customer.name, "account": row.financial_account.name,
                         "amount": money(row.amount), "currency": row.currency.title, "base_amount": money(row.base_amount),
                         "allocated": money(row.allocated_amount), "unallocated": money(row.unallocated_amount), "reference": row.reference}
                        for row in receipts]
        payments = SupplierPayment.objects.filter(shop=shop, status=SupplierPayment.Status.POSTED, payment_date__range=(start, end)).select_related("supplier", "financial_account", "currency").prefetch_related("allocations")
        payment_rows = [{"date": row.payment_date, "supplier": row.supplier.name, "account": row.financial_account.name,
                         "amount": money(row.amount), "currency": row.currency.title, "base_amount": money(row.base_amount),
                         "service_charge": money(row.service_charge), "allocated": money(row.allocated_amount), "reference": row.reference}
                        for row in payments]
        charges = defaultdict(Decimal)
        for row in payments:
            charges[row.currency.title] += row.service_charge
        for row in transfers:
            charges[row.from_currency.title] += row.service_charge
        return Response({"shop": shop.name, "start_date": start, "end_date": end,
                         "summary": {"balances_by_currency": [{"currency": key, "balance": money(value)} for key, value in sorted(totals.items())],
                                     "receipts_base": money(sum((row.base_amount for row in receipts), Decimal("0"))),
                                     "payments_base": money(sum((row.base_amount for row in payments), Decimal("0"))),
                                     "service_charges_by_currency": [{"currency": key, "amount": money(value)} for key, value in sorted(charges.items())]},
                         "accounts": account_rows, "transactions": transaction_rows, "transfers": transfer_rows,
                         "receipts": receipt_rows, "payments": payment_rows})
