from decimal import Decimal

from django.db import transaction
from django.db.models import F, Q, Sum
from django.utils import timezone
from django_filters.rest_framework import DjangoFilterBackend
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.exceptions import ValidationError
from rest_framework.filters import SearchFilter
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response

from account.permissions import (
    HasActionPermission,
    HasTabPermission,
    accessible_shops,
    user_can_see_all_shop_data,
)
from shop_management.models.data.account_transaction import (
    AccountTransaction,
    AccountTransactionType,
)
from shop_management.models.data.inventory import StockMovement
from shop_management.models.data.opening_balance import FinancialOpeningBalance
from shop_management.models.data.product import Product
from shop_management.models.data.purchase import Purchase
from shop_management.models.data.sale import Sale
from shop_management.models.data.shop_account import Account
from shop_management.models.data.supplier_payment import SupplierPayment
from shop_management.serializers.data.account_transaction import (
    AccountTransactionSerializer,
)
from shop_management.views.data.base import DataRootViewSet
from shop_management.views.data.pagination import CustomPagination


class AccountTransactionViewSet(DataRootViewSet):
    serializer_class = AccountTransactionSerializer
    permission_classes = [IsAuthenticated, HasTabPermission, HasActionPermission]
    required_tab = "account_transactions"
    action_permission_map = {
        "create": "accounting.create_manual_journal",
        "reverse": "accounting.void_journal",
    }
    filter_backends = [DjangoFilterBackend, SearchFilter]
    filterset_fields = [
        "account",
        "account_currency",
        "transaction_currency",
        "transaction_type",
        "transaction_date",
        "source_type",
        "is_system_generated",
    ]
    search_fields = ["description", "account__name"]
    pagination_class = CustomPagination

    def get_queryset(self):
        qs = AccountTransaction.objects.select_related(
            "account",
            "account__shop",
            "account_currency",
            "transaction_currency",
            "created_by",
            "reversed_by",
        ).order_by("-transaction_date", "-id")
        if str(self.request.query_params.get("include_system", "")).lower() not in {"1", "true", "yes"}:
            qs = qs.filter(account__is_system_account=False)
        if user_can_see_all_shop_data(self.request.user, self.required_tab):
            return qs
        return qs.filter(
            Q(account__shop__owner=self.request.user)
            | Q(account__shop__assigned_users=self.request.user)
        ).distinct()

    def perform_create(self, serializer):
        account = serializer.validated_data["account"]
        if serializer.validated_data["transaction_type"] == AccountTransactionType.DEBIT:
            current = self._account_balance(account)
            debit = serializer.validated_data["converted_amount"]
            if current - debit < 0 and not account.allow_overdraft:
                raise ValidationError(
                    {
                        "amount": (
                            f"This debit would make {account.name} negative. Available balance is "
                            f"{current} {account.currency.title}. Enable overdraft on the account only "
                            "when a negative Sarafi or bank balance is intentional."
                        )
                    }
                )
        serializer.save(
            created_by=self.request.user,
            source_type="MANUAL",
            source_id="",
            is_system_generated=False,
        )

    def destroy(self, request, *args, **kwargs):
        return Response(
            {"detail": "Financial transactions cannot be deleted. Use Reverse and provide a reason."},
            status=status.HTTP_400_BAD_REQUEST,
        )

    @staticmethod
    def _account_balance(account):
        # A reversal is a separate opposite entry. Keep both original and reversal
        # in the arithmetic so their effects net to zero while the audit trail remains.
        totals = account.transactions.aggregate(
            credits=Sum("converted_amount", filter=Q(transaction_type="credit")),
            debits=Sum("converted_amount", filter=Q(transaction_type="debit")),
        )
        return (
            Decimal(account.opening_balance or 0)
            + Decimal(totals["credits"] or 0)
            - Decimal(totals["debits"] or 0)
        )

    @action(detail=True, methods=["post"])
    def reverse(self, request, pk=None):
        original = self.get_object()
        reason = str(request.data.get("reason") or "").strip()
        if len(reason) < 5:
            raise ValidationError({"reason": "Give a clear reversal reason (at least 5 characters)."})
        if original.reversed_at:
            raise ValidationError({"detail": "This transaction has already been reversed."})
        if original.reversed_transaction_id:
            raise ValidationError({"detail": "A reversal entry cannot itself be reversed."})
        if original.is_system_generated:
            raise ValidationError({
                "detail": (
                    f"This entry came from {original.source_type}. Cancel the source document instead "
                    "so its account movement, journal, supplier/customer balance and stock remain consistent."
                )
            })

        opposite = (
            AccountTransactionType.DEBIT
            if original.transaction_type == AccountTransactionType.CREDIT
            else AccountTransactionType.CREDIT
        )
        with transaction.atomic():
            reversal = AccountTransaction.objects.create(
                account=original.account,
                account_currency=original.account_currency,
                transaction_currency=original.transaction_currency,
                transaction_type=opposite,
                amount=original.amount,
                exchange_rate=original.exchange_rate,
                converted_amount=original.converted_amount,
                description=f"Reversal of transaction #{original.pk}: {reason}",
                reference=original.reference,
                source_type="REVERSAL",
                source_id=str(original.pk),
                is_system_generated=True,
                created_by=request.user,
                reversed_transaction=original,
                transaction_date=timezone.localdate(),
            )
            original.reversed_at = timezone.now()
            original.reversed_by = request.user
            original.reversal_reason = reason
            original.save(update_fields=["reversed_at", "reversed_by", "reversal_reason", "updated_at"])
        return Response(self.get_serializer(reversal).data, status=status.HTTP_201_CREATED)

    @action(detail=False, methods=["get"])
    def audit(self, request):
        shops = accessible_shops(request.user, request)
        shop_id = request.query_params.get("shop")
        if shop_id:
            shops = shops.filter(pk=shop_id)

        accounts = Account.objects.filter(shop__in=shops, is_system_account=False).select_related("shop", "currency")
        transactions = AccountTransaction.objects.filter(account__shop__in=shops, account__is_system_account=False)
        issues = []

        for account in accounts:
            balance = self._account_balance(account)
            if balance < 0:
                issues.append({
                    "severity": "warning" if account.allow_overdraft else "critical",
                    "category": "Negative account",
                    "branch": account.shop.name,
                    "title": f"{account.name} is below zero",
                    "detail": f"Current balance: {balance} {account.currency.title}. Overdraft is {'allowed' if account.allow_overdraft else 'not allowed'}.",
                    "route": "/shop/accounts",
                })
            controlled_opening_exists = FinancialOpeningBalance.objects.filter(
                shop=account.shop,
                financial_account=account,
                status=FinancialOpeningBalance.Status.POSTED,
            ).exists()
            if account.opening_balance and not controlled_opening_exists:
                issues.append({
                    "severity": "warning",
                    "category": "Opening balance",
                    "branch": account.shop.name,
                    "title": f"{account.name} uses a legacy direct opening balance",
                    "detail": "This starting balance is visible, but it has no controlled opening document, poster, or cancellation trail. Verify it during migration review.",
                    "route": "/shop/financial-opening-balances",
                })

        missing_units = Product.objects.filter(shop__in=shops, default_unit__isnull=True)
        for product in missing_units.select_related("shop")[:100]:
            issues.append({
                "severity": "critical",
                "category": "Product setup",
                "branch": product.shop.name,
                "title": f"{product.name} has no unit of measure",
                "detail": "Purchases and sales are blocked until a default unit is selected.",
                "route": "/shop/products",
            })

        for row in transactions.filter(
            Q(description__isnull=True) | Q(description="") | Q(created_by__isnull=True)
        ).select_related("account", "account__shop", "transaction_currency")[:100]:
            missing = []
            if not row.description:
                missing.append("description")
            if not row.created_by_id:
                missing.append("creator (legacy record)")
            issues.append({
                "severity": "warning",
                "category": "Audit trail",
                "branch": row.account.shop.name,
                "title": f"Transaction #{row.pk} is missing {', '.join(missing)}",
                "detail": f"Account: {row.account.name}; amount: {row.amount} {row.transaction_currency.title}.",
                "route": "/shop/account-transactions",
            })

        for payment in SupplierPayment.objects.filter(
            shop__in=shops, service_charge__gt=0
        ).select_related("shop", "supplier", "currency")[:100]:
            ratio = (Decimal(payment.service_charge) / Decimal(payment.amount)) if payment.amount else Decimal("0")
            if not payment.notes or ratio > Decimal("0.05"):
                issues.append({
                    "severity": "critical" if ratio > Decimal("0.25") else "warning",
                    "category": "Supplier fee",
                    "branch": payment.shop.name,
                    "title": f"Review fee on {payment.reference or f'payment #{payment.pk}'}",
                    "detail": f"Supplier amount {payment.amount}; additional fee {payment.service_charge} {payment.currency.title} ({ratio * 100:.1f}%).",
                    "route": "/shop/supplier-payments",
                })

        draft_sales = Sale.objects.filter(shop__in=shops, document_status=Sale.DocumentStatus.DRAFT)
        draft_purchases = Purchase.objects.filter(shop__in=shops, document_status=Purchase.DocumentStatus.DRAFT)
        draft_sale_ids = list(draft_sales.values_list("id", flat=True))
        draft_purchase_ids = list(draft_purchases.values_list("id", flat=True))
        affected_drafts = StockMovement.objects.filter(
            warehouse__shop__in=shops
        ).filter(
            Q(reference_type__iexact="sale", reference_id__in=[str(v) for v in draft_sale_ids])
            | Q(reference_type__iexact="purchase", reference_id__in=[str(v) for v in draft_purchase_ids])
        ).count()
        if affected_drafts:
            issues.append({
                "severity": "critical",
                "category": "Draft integrity",
                "branch": "Multiple",
                "title": f"{affected_drafts} stock movement(s) are linked to draft documents",
                "detail": "Drafts must not change controlled stock. Review these records before posting more documents.",
                "route": "/shop/stock",
            })

        cross_currency_rate_one = transactions.filter(
            exchange_rate=1
        ).exclude(transaction_currency=F("account_currency")).count()
        if cross_currency_rate_one:
            issues.append({
                "severity": "warning",
                "category": "Exchange rate",
                "branch": "Multiple",
                "title": f"{cross_currency_rate_one} cross-currency movement(s) used rate 1",
                "detail": "Verify these rates; rate 1 is normally only expected when both currencies are the same.",
                "route": "/shop/account-transactions",
            })

        counts = {
            "issues": len(issues),
            "critical": sum(1 for issue in issues if issue["severity"] == "critical"),
            "warnings": sum(1 for issue in issues if issue["severity"] == "warning"),
            "negative_accounts": sum(1 for account in accounts if self._account_balance(account) < 0),
            "draft_sales": draft_sales.count(),
            "draft_purchases": draft_purchases.count(),
            "missing_units": missing_units.count(),
        }
        return Response({"summary": counts, "issues": issues})
