from datetime import timedelta
from decimal import Decimal

from django.db.models import Sum
from django.utils import timezone

from apps.business.orders.models import Order
from apps.business.pool.models import PoolEntry
from apps.business.schemes.models import OpportunityBundleDistribution
from apps.business.schemes.services.distribution_service import (
    calculate_distribution,
    resolve_distribution_base_amount,
)
from apps.business.wallet.models import WalletLedger


class DistributionMetricsService:
    COMPONENTS = [
        OpportunityBundleDistribution.ComponentType.DIRECT_INCENTIVE,
        OpportunityBundleDistribution.ComponentType.ENGAGEMENT_POOL,
        OpportunityBundleDistribution.ComponentType.POWER_STREAM,
        OpportunityBundleDistribution.ComponentType.PLATFORM_CHARGES,
        OpportunityBundleDistribution.ComponentType.TAX,
    ]

    LABELS = {
        OpportunityBundleDistribution.ComponentType.DIRECT_INCENTIVE: "Direct Incentive",
        OpportunityBundleDistribution.ComponentType.ENGAGEMENT_POOL: "Growth Pool",
        OpportunityBundleDistribution.ComponentType.POWER_STREAM: "Power Stream",
        OpportunityBundleDistribution.ComponentType.PLATFORM_CHARGES: "Platform Charges",
        OpportunityBundleDistribution.ComponentType.TAX: "Tax",
    }

    @staticmethod
    def _zero_map():
        return {component: Decimal("0.00") for component in DistributionMetricsService.COMPONENTS}

    @staticmethod
    def expected_component_totals(orders_qs):
        """
        Project expected split totals from opportunity_bundle distribution logic.

        Uses completed order records and per-order resolved distribution base amount.
        """
        totals = DistributionMetricsService._zero_map()
        if not orders_qs.exists():
            return totals

        distribution_cache = {}

        for order in orders_qs.select_related("product__opportunity_bundle"):
            product = getattr(order, "product", None)
            opportunity_bundle = getattr(product, "opportunity_bundle", None) if product else None
            if not product or not opportunity_bundle:
                continue

            base_amount = resolve_distribution_base_amount(product, order.amount)
            cache_key = f"{opportunity_bundle.id}:{base_amount}"

            if cache_key not in distribution_cache:
                try:
                    distribution_cache[cache_key] = calculate_distribution(product, base_amount).get("breakdown", [])
                except Exception:
                    distribution_cache[cache_key] = []

            for split in distribution_cache[cache_key]:
                split_type = split.get("type")
                if split_type in totals:
                    totals[split_type] += Decimal(str(split.get("amount") or 0))

        return totals

    @staticmethod
    def projected_net_component_totals(orders_qs):
        """
        Project the net payout total for components that are credited net after deductions.

        Direct incentive and power stream are ledgered after a fixed 25% deduction,
        while growth pool remains gross and platform/tax are still projected gross.
        """
        totals = DistributionMetricsService._zero_map()
        if not orders_qs.exists():
            return totals

        net_components = {
            OpportunityBundleDistribution.ComponentType.DIRECT_INCENTIVE,
            OpportunityBundleDistribution.ComponentType.POWER_STREAM,
        }

        gross_totals = DistributionMetricsService.expected_component_totals(orders_qs)
        for component, gross_total in gross_totals.items():
            if component in net_components:
                totals[component] = (gross_total * Decimal("0.75")).quantize(Decimal("0.01"))
            else:
                totals[component] = gross_total

        return totals

    @staticmethod
    def _actual_wallet_totals(start_dt=None, end_dt=None):
        """
        Actual realized wallet movements for components that are ledger-backed.
        """
        scope = WalletLedger.objects.filter(
            wallet_type=WalletLedger.WalletType.EARNINGS,
            source_type__in=[
                WalletLedger.SourceType.DIRECT_INCENTIVE,
                WalletLedger.SourceType.POWER_STREAM,
            ],
        )
        if start_dt:
            scope = scope.filter(created_at__gte=start_dt)
        if end_dt:
            scope = scope.filter(created_at__lt=end_dt)

        rows = scope.values("source_type", "transaction_type").annotate(total=Sum("amount"))

        actual = {
            component: {"credits": Decimal("0.00"), "debits": Decimal("0.00")}
            for component in DistributionMetricsService.COMPONENTS
        }

        source_to_component = {
            WalletLedger.SourceType.DIRECT_INCENTIVE: OpportunityBundleDistribution.ComponentType.DIRECT_INCENTIVE,
            WalletLedger.SourceType.POWER_STREAM: OpportunityBundleDistribution.ComponentType.POWER_STREAM,
        }

        for row in rows:
            component = source_to_component.get(row.get("source_type"))
            if not component:
                continue
            amount = Decimal(str(row.get("total") or 0))
            if row.get("transaction_type") == WalletLedger.TransactionType.CREDIT:
                actual[component]["credits"] += amount
            else:
                actual[component]["debits"] += amount

        return actual

    @staticmethod
    def _actual_pool_totals(start_dt=None, end_dt=None, opportunity_bundle_id=None):
        scope = PoolEntry.objects.all()
        if opportunity_bundle_id:
            scope = scope.filter(opportunity_bundle_id=opportunity_bundle_id)
        if start_dt:
            scope = scope.filter(created_at__gte=start_dt)
        if end_dt:
            scope = scope.filter(created_at__lt=end_dt)

        credits = scope.aggregate(total=Sum("amount"))["total"] or Decimal("0.00")
        return {
            "credits": Decimal(str(credits)),
            "debits": Decimal("0.00"),
            "entries": scope.count(),
        }

    @staticmethod
    def _orders_scope(start_dt=None, end_dt=None, opportunity_bundle_id=None):
        scope = Order.objects.filter(status="COMPLETED")
        if start_dt:
            scope = scope.filter(created_at__gte=start_dt)
        if end_dt:
            scope = scope.filter(created_at__lt=end_dt)
        if opportunity_bundle_id:
            scope = scope.filter(product__opportunity_bundle_id=opportunity_bundle_id)
        return scope

    @staticmethod
    def _window_metrics(start_dt=None, end_dt=None, opportunity_bundle_id=None):
        orders_scope = DistributionMetricsService._orders_scope(
            start_dt=start_dt,
            end_dt=end_dt,
            opportunity_bundle_id=opportunity_bundle_id,
        )
        expected = DistributionMetricsService.projected_net_component_totals(orders_scope)

        wallet_actual = DistributionMetricsService._actual_wallet_totals(start_dt=start_dt, end_dt=end_dt)
        pool_actual = DistributionMetricsService._actual_pool_totals(start_dt=start_dt, end_dt=end_dt, opportunity_bundle_id=opportunity_bundle_id)

        components = []
        for component in DistributionMetricsService.COMPONENTS:
            expected_total = expected.get(component, Decimal("0.00"))
            actual_credits = wallet_actual.get(component, {}).get("credits", Decimal("0.00"))
            actual_debits = wallet_actual.get(component, {}).get("debits", Decimal("0.00"))

            if component == OpportunityBundleDistribution.ComponentType.ENGAGEMENT_POOL:
                actual_credits = pool_actual["credits"]
                actual_debits = pool_actual["debits"]

            actual_net = actual_credits - actual_debits
            variance = expected_total - actual_net

            components.append(
                {
                    "component": component,
                    "label": DistributionMetricsService.LABELS.get(component, component),
                    "configured_total": expected_total,
                    "actual_credits": actual_credits,
                    "actual_debits": actual_debits,
                    "actual_net": actual_net,
                    "variance": variance,
                    "entry_count": pool_actual["entries"] if component == OpportunityBundleDistribution.ComponentType.ENGAGEMENT_POOL else None,
                    "tracking_note": (
                        "Actuals are not yet ledger-tracked for this component."
                        if component in [
                            OpportunityBundleDistribution.ComponentType.PLATFORM_CHARGES,
                            OpportunityBundleDistribution.ComponentType.TAX,
                        ]
                        else ""
                    ),
                }
            )

        return {
            "orders_count": orders_scope.count(),
            "orders_amount": orders_scope.aggregate(total=Sum("amount"))["total"] or Decimal("0.00"),
            "components": components,
        }

    @staticmethod
    def _stringify_window(window):
        formatted = {
            "orders_count": window["orders_count"],
            "orders_amount": str(window["orders_amount"]),
            "components": [],
        }
        for item in window["components"]:
            formatted["components"].append(
                {
                    **item,
                    "configured_total": str(item["configured_total"]),
                    "actual_credits": str(item["actual_credits"]),
                    "actual_debits": str(item["actual_debits"]),
                    "actual_net": str(item["actual_net"]),
                    "variance": str(item["variance"]),
                }
            )
        return formatted

    @staticmethod
    def get_metrics(opportunity_bundle_id=None):
        now = timezone.now()
        start_today = now.replace(hour=0, minute=0, second=0, microsecond=0)
        start_7d = now - timedelta(days=7)
        start_30d = now - timedelta(days=30)

        all_time = DistributionMetricsService._window_metrics(opportunity_bundle_id=opportunity_bundle_id)
        today = DistributionMetricsService._window_metrics(start_dt=start_today, opportunity_bundle_id=opportunity_bundle_id)
        last_7d = DistributionMetricsService._window_metrics(start_dt=start_7d, opportunity_bundle_id=opportunity_bundle_id)
        last_30d = DistributionMetricsService._window_metrics(start_dt=start_30d, opportunity_bundle_id=opportunity_bundle_id)

        return {
            "generated_at": now.isoformat(),
            "filters": {
                "opportunity_bundle_id": str(opportunity_bundle_id) if opportunity_bundle_id else None,
                "wallet_tracking_scope": (
                    "all_schemes" if not opportunity_bundle_id else "all_schemes_for_wallet_components"
                ),
            },
            "all_time": DistributionMetricsService._stringify_window(all_time),
            "periodic": {
                "today": DistributionMetricsService._stringify_window(today),
                "last_7d": DistributionMetricsService._stringify_window(last_7d),
                "last_30d": DistributionMetricsService._stringify_window(last_30d),
            },
        }
