from django.shortcuts import render, get_object_or_404
from django.db.models import Q
from hub.services.models import (
    ServiceOffering,
    CloudProvider,
    Category,
    Service,
    ComputePlan,
    VSHNAppCatPrice,
)
import re
from collections import defaultdict


def natural_sort_key(name):
    """Extract numeric part from compute plan name for natural sorting"""
    match = re.search(r"compute-std-(\d+)", name)
    return int(match.group(1)) if match else 0


def offering_list(request):
    offerings = (
        ServiceOffering.objects.filter(disable_listing=False)
        .order_by("service")
        .select_related("service", "cloud_provider")
        .prefetch_related(
            "service__categories",
            "plans",
        )
    )

    cloud_providers = CloudProvider.objects.all()
    categories = Category.objects.filter(parent=None).prefetch_related("children")
    services = Service.objects.all().order_by("name")

    # Handle cloud provider filter
    if request.GET.get("cloud_provider"):
        provider_id = request.GET.get("cloud_provider")
        offerings = offerings.filter(cloud_provider_id=provider_id)

    # Handle category filter
    if request.GET.get("category"):
        category_id = request.GET.get("category")
        category = get_object_or_404(Category, id=category_id)
        subcategories = Category.objects.filter(parent=category)
        offerings = offerings.filter(
            Q(service__categories=category) | Q(service__categories__in=subcategories)
        ).distinct()

    # Handle service filter
    if request.GET.get("service"):
        service_id = request.GET.get("service")
        offerings = offerings.filter(service_id=service_id)

    # Handle search
    if request.GET.get("search"):
        query = request.GET.get("search")
        offerings = offerings.filter(
            Q(service__name__icontains=query)
            | Q(description__icontains=query)
            | Q(cloud_provider__name__icontains=query)
        ).distinct()

    context = {
        "offerings": offerings,
        "cloud_providers": cloud_providers,
        "categories": categories,
        "services": services,
    }
    return render(request, "services/offering_list.html", context)


def offering_detail(request, provider_slug, service_slug):
    offering = get_object_or_404(
        ServiceOffering.objects.select_related(
            "service", "cloud_provider"
        ).prefetch_related("plans"),
        cloud_provider__slug=provider_slug,
        service__slug=service_slug,
    )

    pricing_data_by_group_and_service_level = None

    # Generate pricing data for VSHN offerings
    if offering.msp == "VS":
        pricing_data_by_group_and_service_level = generate_pricing_data(offering)

    context = {
        "offering": offering,
        "pricing_data_by_group_and_service_level": pricing_data_by_group_and_service_level,
    }
    return render(request, "services/offering_detail.html", context)


def generate_pricing_data(offering):
    """Generate pricing data for a specific offering and cloud provider"""
    # Fetch compute plans for this cloud provider
    compute_plans = (
        ComputePlan.objects.filter(active=True, cloud_provider=offering.cloud_provider)
        .select_related("cloud_provider", "group")
        .prefetch_related("prices")
        .order_by("group__order", "group__name")
    )

    # Apply natural sorting for compute plan names
    compute_plans = sorted(
        compute_plans,
        key=lambda x: (
            x.group.order if x.group else 999,
            x.group.name if x.group else "ZZZ",
            natural_sort_key(x.name),
        ),
    )

    # Fetch pricing for this specific service
    try:
        appcat_price = (
            VSHNAppCatPrice.objects.select_related("service", "discount_model")
            .prefetch_related("base_fees", "unit_rates", "discount_model__tiers")
            .get(service=offering.service)
        )
    except VSHNAppCatPrice.DoesNotExist:
        return None

    pricing_data_by_group_and_service_level = defaultdict(lambda: defaultdict(list))
    processed_combinations = set()

    # Generate pricing combinations for each compute plan
    for plan in compute_plans:
        plan_currencies = set(plan.prices.values_list("currency", flat=True))

        # Determine units based on variable unit type
        if appcat_price.variable_unit == VSHNAppCatPrice.VariableUnit.RAM:
            units = int(plan.ram)
        elif appcat_price.variable_unit == VSHNAppCatPrice.VariableUnit.CPU:
            units = int(plan.vcpus)
        else:
            continue

        base_fee_currencies = set(
            appcat_price.base_fees.values_list("currency", flat=True)
        )

        service_levels = appcat_price.unit_rates.values_list(
            "service_level", flat=True
        ).distinct()

        for service_level in service_levels:
            unit_rate_currencies = set(
                appcat_price.unit_rates.filter(service_level=service_level).values_list(
                    "currency", flat=True
                )
            )

            # Find currencies that exist across all pricing components
            matching_currencies = plan_currencies.intersection(
                base_fee_currencies
            ).intersection(unit_rate_currencies)

            if not matching_currencies:
                continue

            for currency in matching_currencies:
                combination_key = (
                    plan.name,
                    service_level,
                    currency,
                )

                # Skip if combination already processed
                if combination_key in processed_combinations:
                    continue

                processed_combinations.add(combination_key)

                # Get pricing components
                compute_plan_price = plan.get_price(currency)
                base_fee = appcat_price.get_base_fee(currency)
                unit_rate = appcat_price.get_unit_rate(currency, service_level)

                # Skip if any pricing component is missing
                if any(
                    price is None for price in [compute_plan_price, base_fee, unit_rate]
                ):
                    continue

                # Calculate replica enforcement based on service level
                if service_level == VSHNAppCatPrice.ServiceLevel.GUARANTEED:
                    replica_enforce = appcat_price.ha_replica_min
                else:
                    replica_enforce = 1

                total_units = units * replica_enforce
                standard_sla_price = base_fee + (total_units * unit_rate)

                # Apply discount if available
                if appcat_price.discount_model and appcat_price.discount_model.active:
                    discounted_price = appcat_price.discount_model.calculate_discount(
                        unit_rate, total_units
                    )
                    sla_price = base_fee + discounted_price
                else:
                    sla_price = standard_sla_price

                final_price = compute_plan_price + sla_price
                service_level_display = dict(VSHNAppCatPrice.ServiceLevel.choices)[
                    service_level
                ]

                group_name = plan.group.name if plan.group else "No Group"

                # Add pricing data to the grouped structure
                pricing_data_by_group_and_service_level[group_name][
                    service_level_display
                ].append(
                    {
                        "compute_plan": plan.name,
                        "compute_plan_group": group_name,
                        "compute_plan_group_description": (
                            plan.group.description if plan.group else ""
                        ),
                        "vcpus": plan.vcpus,
                        "ram": plan.ram,
                        "currency": currency,
                        "compute_plan_price": compute_plan_price,
                        "sla_price": sla_price,
                        "final_price": final_price,
                    }
                )

    # Order groups correctly, placing "No Group" last
    ordered_groups = {}
    all_group_names = list(pricing_data_by_group_and_service_level.keys())

    if "No Group" in all_group_names:
        all_group_names.remove("No Group")
        all_group_names.append("No Group")

    for group_name_key in all_group_names:
        ordered_groups[group_name_key] = pricing_data_by_group_and_service_level[
            group_name_key
        ]

    # Convert defaultdicts to regular dicts for the template
    final_context_data = {}
    for group_key, service_levels_dict in ordered_groups.items():
        final_context_data[group_key] = {
            sl_key: list(plans_list)
            for sl_key, plans_list in service_levels_dict.items()
        }

    return final_context_data
