from collections import defaultdict
from datetime import date, datetime, time, timedelta
from decimal import Decimal

from django.db.models import Q
from django.utils import timezone
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from rest_framework.views import APIView

from account.permissions import get_user_data_filter, get_user_shop_filter
from shop_management.models.data.customer_balance_payment import CustomerBalancePayment
from shop_management.models.data.daily_diary import DailyDiaryEntry
from shop_management.models.data.expense import Expense
from shop_management.models.data.purchase import Purchase
from shop_management.models.data.purchase_payment import PurchasePayment
from shop_management.models.data.purchase_return import PurchaseReturn
from shop_management.models.data.quotation import Quotation
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.shop import Shop
from shop_management.models.data.stock_transfer import StockTransfer
from shop_management.models.data.supplier_balance_payment import SupplierBalancePayment
from shop_management.serializers.data.daily_diary import DailyDiaryEntrySerializer
from shop_management.services.reporting import reportable_sale_filter
from shop_management.views.reports.financial_report import FinancialReportView


class DailyDiaryReportView(APIView):
    permission_classes = [IsAuthenticated]

    @staticmethod
    def _parse_target_date(request):
        raw = request.query_params.get("date")
        if raw:
            try:
                return date.fromisoformat(raw)
            except (ValueError, TypeError):
                pass
        now = timezone.localtime()
        if now.hour < 12:
            return (now - timedelta(days=1)).date()
        return now.date()

    @staticmethod
    def _day_window_label(target_date, day_cycle):
        if day_cycle == "noon":
            end = target_date + timedelta(days=1)
            return f"{target_date.isoformat()} 12:00 PM → {end.isoformat()} 12:00 PM"
        return f"{target_date.isoformat()} 00:00 → {target_date.isoformat()} 23:59"

    @staticmethod
    def _format_time(value):
        if value is None:
            return ""
        if isinstance(value, datetime):
            if timezone.is_naive(value):
                value = timezone.make_aware(value, timezone.get_current_timezone())
            return timezone.localtime(value).strftime("%H:%M")
        return ""

    @staticmethod
    def _activity_sort_key(activity):
        return activity.get("sort_at") or ""

    @staticmethod
    def _totals_to_list(totals):
        return [
            {"currency": currency, "amount": str(amount)}
            for currency, amount in sorted(totals.items())
        ]

    @staticmethod
    def _add_to_totals(bucket, currency_label, amount):
        if amount is None:
            return
        bucket[currency_label] += Decimal(str(amount))

    @staticmethod
    def _summary_block(count, totals):
        return {"count": count, "totals": DailyDiaryReportView._totals_to_list(totals)}

    def _build_shop_date_filter(self, user, shop_field, date_field, start_dt, end_dt, start, end, day_cycle, selected_shop):
        f = get_user_data_filter(user, shop_field)
        if selected_shop:
            f &= Q(**{f"{shop_field}_id": selected_shop})

        is_datetime_date = date_field.endswith("__date")
        lookup_field = date_field[: -len("__date")] if is_datetime_date else date_field
        gte_value = start_dt if is_datetime_date else start
        lte_value = end_dt if is_datetime_date else end
        if gte_value:
            f &= Q(**{f"{lookup_field}__gte": gte_value})
        if lte_value:
            f &= Q(**{f"{lookup_field}__lte": lte_value})
        return f

    def _stock_transfer_filter(self, user, start_dt, end_dt, selected_shop):
        f = Q()
        if selected_shop:
            f &= Q(supplier_branch_id=selected_shop) | Q(customer_branch_id=selected_shop)
        else:
            allowed = Shop.objects.filter(get_user_shop_filter(user)).values_list("id", flat=True)
            f &= Q(supplier_branch_id__in=allowed) | Q(customer_branch_id__in=allowed)
        if start_dt:
            f &= Q(transfer_datetime__gte=start_dt)
        if end_dt:
            f &= Q(transfer_datetime__lte=end_dt)
        return f

    def _customer_payment_filter(self, user, start_dt, end_dt, selected_shop):
        f = Q()
        if selected_shop:
            f &= Q(customer_branch_id=selected_shop) | Q(supplier_branch_id=selected_shop)
        else:
            allowed = Shop.objects.filter(get_user_shop_filter(user)).values_list("id", flat=True)
            f &= Q(customer_branch_id__in=allowed) | Q(supplier_branch_id__in=allowed)
        if start_dt:
            f &= Q(payment_date__gte=start_dt)
        if end_dt:
            f &= Q(payment_date__lte=end_dt)
        return f

    def _supplier_payment_filter(self, user, start_dt, end_dt, selected_shop):
        f = get_user_data_filter(user, "supplier__shop")
        if selected_shop:
            f &= Q(supplier__shop_id=selected_shop)
        if start_dt:
            f &= Q(payment_date__gte=start_dt)
        if end_dt:
            f &= Q(payment_date__lte=end_dt)
        return f

    def get(self, request):
        user = request.user
        target_date = self._parse_target_date(request)
        selected_shop = request.query_params.get("shop") or None
        day_cycle = FinancialReportView._parse_day_cycle(request)
        start_dt, end_dt = FinancialReportView._local_datetime_bounds(
            target_date, target_date, day_cycle
        )

        def shop_date_filter(shop_field, date_field):
            return self._build_shop_date_filter(
                user, shop_field, date_field, start_dt, end_dt,
                target_date, target_date, day_cycle, selected_shop,
            )

        activities = []

        # Sales
        sales = Sale.objects.filter(
            shop_date_filter("shop", "sale_date"),
            reportable_sale_filter(),
        ).select_related(
            "customer", "currency", "shop"
        )
        sale_totals = defaultdict(Decimal)
        for sale in sales:
            currency = FinancialReportView._currency_label(sale.currency)
            amount = sale.effective_total_amount if hasattr(sale, "effective_total_amount") else Decimal("0")
            if isinstance(amount, dict):
                amount = sum(Decimal(str(v)) for v in amount.values())
            else:
                amount = Decimal(str(amount or 0))
            self._add_to_totals(sale_totals, currency, amount)
            party = sale.customer.name if sale.customer else (sale.general_customer_name or "Walk-in")
            activities.append({
                "type": "sale",
                "id": sale.id,
                "time": "",
                "sort_at": str(sale.sale_date),
                "label": f"Sale #{sale.id}",
                "party": party,
                "amount": str(amount),
                "currency": currency,
            })

        # Purchases
        purchases = Purchase.objects.filter(
            shop_date_filter("shop", "purchase_date"),
            is_deleted=False,
        ).select_related("supplier", "currency", "shop")
        purchase_totals = defaultdict(Decimal)
        for purchase in purchases:
            currency = FinancialReportView._currency_label(purchase.currency)
            amount = Decimal(str(purchase.total_price or 0))
            self._add_to_totals(purchase_totals, currency, amount)
            party = purchase.supplier.name if purchase.supplier else "-"
            activities.append({
                "type": "purchase",
                "id": purchase.id,
                "time": "",
                "sort_at": str(purchase.purchase_date),
                "label": f"Purchase #{purchase.id}",
                "party": party,
                "amount": str(amount),
                "currency": currency,
            })

        # Quotations
        quotations = Quotation.objects.filter(
            shop_date_filter("shop", "quotation_date"),
            is_deleted=False,
        ).select_related("customer", "currency", "shop")
        quotation_totals = defaultdict(Decimal)
        for quotation in quotations:
            currency = FinancialReportView._currency_label(quotation.currency)
            amount = Decimal(str(quotation.effective_total_amount or 0))
            self._add_to_totals(quotation_totals, currency, amount)
            party = (
                quotation.customer.name if quotation.customer
                else (quotation.visitor_name or "Visitor")
            )
            activities.append({
                "type": "quotation",
                "id": quotation.id,
                "time": "",
                "sort_at": str(quotation.quotation_date),
                "label": f"Quotation #{quotation.id}",
                "party": party,
                "amount": str(amount),
                "currency": currency,
            })

        # Sale payments (inflow)
        sale_payments = SalePayment.objects.filter(
            shop_date_filter("sale__shop", "payment_date__date"),
            reportable_sale_filter("sale__"),
        ).select_related("sale", "sale__customer", "currency")
        payments_in_totals = defaultdict(Decimal)
        for payment in sale_payments:
            currency = FinancialReportView._currency_label(payment.currency)
            amount = Decimal(str(payment.amount or 0))
            self._add_to_totals(payments_in_totals, currency, amount)
            party = (
                payment.sale.customer.name if payment.sale and payment.sale.customer
                else "Customer"
            )
            sort_at = payment.payment_date.isoformat() if payment.payment_date else ""
            activities.append({
                "type": "sale_payment",
                "id": payment.id,
                "time": self._format_time(payment.payment_date),
                "sort_at": sort_at,
                "label": f"Sale Payment #{payment.id}",
                "party": party,
                "amount": str(amount),
                "currency": currency,
            })

        # Customer balance payments (inflow)
        customer_payments = CustomerBalancePayment.objects.filter(is_void=False).filter(
            self._customer_payment_filter(user, start_dt, end_dt, selected_shop),
        ).select_related("customer", "currency")
        for payment in customer_payments:
            currency = FinancialReportView._currency_label(payment.currency)
            amount = Decimal(str(payment.amount or 0))
            self._add_to_totals(payments_in_totals, currency, amount)
            party = payment.customer.name if payment.customer else "Customer"
            sort_at = payment.payment_date.isoformat() if payment.payment_date else ""
            activities.append({
                "type": "customer_payment",
                "id": payment.id,
                "time": self._format_time(payment.payment_date),
                "sort_at": sort_at,
                "label": f"Customer Payment #{payment.id}",
                "party": party,
                "amount": str(amount),
                "currency": currency,
            })

        # Purchase payments (outflow)
        purchase_payments = PurchasePayment.objects.filter(
            shop_date_filter("purchase__shop", "payment_date__date"),
        ).select_related("purchase", "purchase__supplier", "currency")
        payments_out_totals = defaultdict(Decimal)
        for payment in purchase_payments:
            currency = FinancialReportView._currency_label(payment.currency)
            amount = Decimal(str(payment.amount or 0))
            self._add_to_totals(payments_out_totals, currency, amount)
            party = (
                payment.purchase.supplier.name
                if payment.purchase and payment.purchase.supplier else "Supplier"
            )
            sort_at = payment.payment_date.isoformat() if payment.payment_date else ""
            activities.append({
                "type": "purchase_payment",
                "id": payment.id,
                "time": self._format_time(payment.payment_date),
                "sort_at": sort_at,
                "label": f"Purchase Payment #{payment.id}",
                "party": party,
                "amount": str(amount),
                "currency": currency,
            })

        # Supplier balance payments (outflow)
        supplier_payments = SupplierBalancePayment.objects.filter(is_void=False).filter(
            self._supplier_payment_filter(user, start_dt, end_dt, selected_shop),
        ).select_related("supplier", "currency")
        for payment in supplier_payments:
            currency = FinancialReportView._currency_label(payment.currency)
            amount = Decimal(str(payment.amount or 0))
            self._add_to_totals(payments_out_totals, currency, amount)
            party = payment.supplier.name if payment.supplier else "Supplier"
            sort_at = payment.payment_date.isoformat() if payment.payment_date else ""
            activities.append({
                "type": "supplier_payment",
                "id": payment.id,
                "time": self._format_time(payment.payment_date),
                "sort_at": sort_at,
                "label": f"Supplier Payment #{payment.id}",
                "party": party,
                "amount": str(amount),
                "currency": currency,
            })

        # Stock transfers
        stock_transfers = StockTransfer.objects.filter(
            self._stock_transfer_filter(user, start_dt, end_dt, selected_shop),
        ).select_related("product", "supplier_branch", "customer_branch")
        stock_totals = defaultdict(Decimal)
        for transfer in stock_transfers:
            amount = Decimal(str(transfer.sale_price or 0)) * Decimal(transfer.transfer_quantity or 0)
            currency = FinancialReportView._currency_label(transfer.currency)
            self._add_to_totals(stock_totals, currency, amount)
            party = f"{transfer.supplier_branch.name} → {transfer.customer_branch.name}"
            sort_at = transfer.transfer_datetime.isoformat() if transfer.transfer_datetime else ""
            activities.append({
                "type": "stock_transfer",
                "id": transfer.id,
                "time": self._format_time(transfer.transfer_datetime),
                "sort_at": sort_at,
                "label": transfer.transfer_id or f"Transfer #{transfer.id}",
                "party": party,
                "amount": str(amount),
                "currency": currency,
            })

        # Expenses
        expenses = Expense.objects.filter(
            shop_date_filter("shop", "payment_date__date"),
        ).select_related("currency", "shop")
        expense_totals = defaultdict(Decimal)
        for expense in expenses:
            currency = FinancialReportView._currency_label(expense.currency)
            amount = Decimal(str(expense.amount or 0))
            self._add_to_totals(expense_totals, currency, amount)
            sort_at = expense.payment_date.isoformat() if expense.payment_date else ""
            activities.append({
                "type": "expense",
                "id": expense.id,
                "time": self._format_time(expense.payment_date),
                "sort_at": sort_at,
                "label": f"Expense #{expense.id}",
                "party": expense.expense_by or "-",
                "amount": str(amount),
                "currency": currency,
            })

        # Sale returns
        sale_returns = SaleReturn.objects.filter(
            shop_date_filter("shop", "return_date__date"),
            reportable_sale_filter("sale__"),
        ).select_related("sale", "shop")
        return_totals = defaultdict(Decimal)
        for ret in sale_returns:
            currency = FinancialReportView._currency_label(
                ret.sale.currency if ret.sale else None
            )
            amount = Decimal(str(ret.total_refound_amount or 0))
            self._add_to_totals(return_totals, currency, amount)
            sort_at = ret.return_date.isoformat() if ret.return_date else ""
            activities.append({
                "type": "sale_return",
                "id": ret.id,
                "time": self._format_time(ret.return_date),
                "sort_at": sort_at,
                "label": f"Sale Return #{ret.id}",
                "party": f"Sale #{ret.sale_id}" if ret.sale_id else "-",
                "amount": str(amount),
                "currency": currency,
            })

        # Purchase returns
        purchase_returns = PurchaseReturn.objects.filter(
            shop_date_filter("shop", "return_date__date"),
        ).select_related("purchase", "shop")
        for ret in purchase_returns:
            currency = FinancialReportView._currency_label(
                ret.purchase.currency if ret.purchase else None
            )
            amount = Decimal(str(ret.total_refund_amount or 0))
            self._add_to_totals(return_totals, currency, amount)
            sort_at = ret.return_date.isoformat() if ret.return_date else ""
            activities.append({
                "type": "purchase_return",
                "id": ret.id,
                "time": self._format_time(ret.return_date),
                "sort_at": sort_at,
                "label": f"Purchase Return #{ret.id}",
                "party": f"Purchase #{ret.purchase_id}" if ret.purchase_id else "-",
                "amount": str(amount),
                "currency": currency,
            })

        activities.sort(key=self._activity_sort_key, reverse=True)
        for activity in activities:
            activity.pop("sort_at", None)

        # Manual diary entry
        manual_entry = None
        diary_qs = DailyDiaryEntry.objects.filter(
            entry_date=target_date,
            is_deleted=False,
        )
        if selected_shop:
            diary_qs = diary_qs.filter(shop_id=selected_shop)
        else:
            diary_qs = diary_qs.filter(get_user_data_filter(user, "shop"))
        entry = diary_qs.select_related("shop", "created_by").first()
        if entry:
            manual_entry = DailyDiaryEntrySerializer(entry).data

        return Response({
            "date": target_date.isoformat(),
            "day_window": self._day_window_label(target_date, day_cycle),
            "day_cycle": day_cycle,
            "manual_entry": manual_entry,
            "summary": {
                "sales": self._summary_block(sales.count(), sale_totals),
                "purchases": self._summary_block(purchases.count(), purchase_totals),
                "quotations": self._summary_block(quotations.count(), quotation_totals),
                "payments_in": self._summary_block(
                    sale_payments.count() + customer_payments.count(),
                    payments_in_totals,
                ),
                "payments_out": self._summary_block(
                    purchase_payments.count() + supplier_payments.count(),
                    payments_out_totals,
                ),
                "stock_transfers": self._summary_block(stock_transfers.count(), stock_totals),
                "expenses": self._summary_block(expenses.count(), expense_totals),
                "returns": self._summary_block(
                    sale_returns.count() + purchase_returns.count(),
                    return_totals,
                ),
            },
            "activities": activities,
        })
