import re
import yaml
from decimal import Decimal

from django.shortcuts import render, get_object_or_404
from django.db.models import Q
from django.http import HttpResponse, JsonResponse
from django.template.loader import render_to_string
from hub.services.models import (
    ServiceOffering,
    CloudProvider,
    Category,
    Service,
    ComputePlan,
    VSHNAppCatPrice,
    StoragePlan,
)
from collections import defaultdict
from markdownify import markdownify


def decimal_to_float(obj):
    """Convert Decimal objects to float for JSON serialization"""
    if isinstance(obj, Decimal):
        return float(obj)
    elif isinstance(obj, dict):
        return {key: decimal_to_float(value) for key, value in obj.items()}
    elif isinstance(obj, list):
        return [decimal_to_float(item) for item in obj]
    return obj


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,
    )

    # Check if JSON pricing data is requested
    if request.GET.get("pricing") == "json":
        pricing_data = None
        if offering.msp == "VS":
            pricing_data = generate_pricing_data(offering)
            if pricing_data:
                # Convert Decimal objects to float for JSON serialization
                pricing_data = decimal_to_float(pricing_data)

        return JsonResponse(pricing_data or {})

    # Check if Exoscale marketplace YAML is requested
    if request.GET.get("exo_marketplace") == "true":
        return generate_exoscale_marketplace_yaml(offering)

    pricing_data_by_group_and_service_level = None
    price_calculator_enabled = False

    # Generate pricing data for VSHN offerings
    if offering.msp == "VS":
        try:
            appcat_price = offering.service.vshn_appcat_price.get()
            price_calculator_enabled = appcat_price.public_display_enabled

            # Only generate pricing data if public display is enabled
            if price_calculator_enabled:
                pricing_data_by_group_and_service_level = generate_pricing_data(
                    offering
                )
        except VSHNAppCatPrice.DoesNotExist:
            pass

    # Get related articles for both cloud provider and service
    provider_articles = offering.cloud_provider.articles.filter(
        is_published=True
    ).order_by("-created_at")[:3]
    service_articles = offering.service.articles.filter(is_published=True).order_by(
        "-created_at"
    )[:3]

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


def generate_exoscale_marketplace_yaml(offering):
    """Generate YAML structure for Exoscale marketplace"""

    # Create service name slug for YAML key
    service_slug = offering.service.slug.replace("-", "")
    yaml_key = f"marketplace_PRODUCTS_servala-{service_slug}"

    # Generate product overview content from service description (convert HTML to Markdown)
    product_overview = ""
    if offering.service.description:
        product_overview = markdownify(
            offering.service.description, heading_style="ATX"
        ).strip()

    # Generate highlights content from offering description and offer_description (convert HTML to Markdown)
    highlights = ""
    if offering.description:
        highlights += markdownify(offering.description, heading_style="ATX").strip()
    if offering.offer_description:
        if highlights:
            highlights += "\n\n"
        highlights += markdownify(
            offering.offer_description.get_full_text(), heading_style="ATX"
        ).strip()

    # Build YAML structure
    yaml_structure = {
        yaml_key: {
            "page_class": "tmpl-marketplace-product",
            "html_title": f"Managed {offering.service.name} by VSHN via Servala",
            "meta_desc": "Servala is the Open Cloud Native Service Hub. It connects businesses, developers, and cloud service providers on one unique hub with secure, scalable, and easy-to-use cloud-native services.",
            "page_header_title": f"Managed {offering.service.name} by VSHN via Servala",
            "provider_key": "vshn",
            "slug": f"servala-managed-{offering.service.slug}",
            "title": f"Managed {offering.service.name} by VSHN via Servala",
            "logo": f"img/servala-{offering.service.slug}.svg",
            "list_display": [],
            "meta": [
                {"key": "exoscale-iaas", "value": True},
                {"key": "availability", "zones": "all"},
            ],
            "action_link": f"https://servala.com/offering/{offering.cloud_provider.slug}/{offering.service.slug}/?source=exoscale_marketplace",
            "action_link_text": "Subscribe now",
            "blobs": [
                {
                    "key": "product-overview",
                    "blob": (
                        product_overview.strip()
                        if product_overview
                        else "Service description not available."
                    ),
                },
                {
                    "key": "highlights",
                    "blob": (
                        highlights.strip()
                        if highlights
                        else "Offering highlights not available."
                    ),
                },
                "editor",
                {
                    "key": "pricing",
                    "blob": f"Find all the pricing information on the [Servala website](https://servala.com/offering/{offering.cloud_provider.slug}/{offering.service.slug}/?source=exoscale_marketplace#plans)",
                },
                {
                    "key": "service-and-support",
                    "blob": "Servala is operated by VSHN AG in Zurich, Switzerland.\n\nSeveral SLAs are available on request, offering support 24/7.\n\nMore details can be found in the [VSHN Service Levels Documentation](https://products.vshn.ch/service_levels.html).",
                },
                {
                    "key": "terms-of-service",
                    "blob": "- [Product Description](https://products.vshn.ch/servala/index.html)\n- [General Terms and Conditions](https://products.vshn.ch/legal/gtc_en.html)\n- [SLA](https://products.vshn.ch/service_levels.html)\n- [DPA](https://products.vshn.ch/legal/dpa_en.html)\n- [Privacy Policy](https://products.vshn.ch/legal/privacy_policy_en.html)",
                },
            ],
        }
    }

    # Generate YAML response for browser display
    yaml_content = yaml.dump(
        yaml_structure,
        default_flow_style=False,
        allow_unicode=True,
        indent=2,
        width=120,
        sort_keys=False,
        default_style=None,
    )

    # Return as plain text for browser display
    response = HttpResponse(yaml_content, content_type="text/plain")

    return response


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 storage plans for this cloud provider
    storage_plans = (
        StoragePlan.objects.filter(cloud_provider=offering.cloud_provider)
        .prefetch_related("prices")
        .order_by("name")
    )

    # Get default storage pricing (use first available storage plan)
    storage_price_data = {}
    if storage_plans.exists():
        default_storage_plan = storage_plans.first()
        for currency in ["CHF", "EUR", "USD"]:  # Add currencies as needed
            price = default_storage_plan.get_price(currency)
            if price is not None:
                storage_price_data[currency] = price

    # 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, service_level)
                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

                # Get addons information
                addons = appcat_price.addons.filter(active=True)
                mandatory_addons = []
                optional_addons = []

                # Calculate additional price from mandatory addons
                addon_total = 0

                for addon in addons:
                    addon_price = None
                    addon_price_per_unit = None

                    if addon.addon_type == "BF":  # Base Fee
                        addon_price = addon.get_price(currency, service_level)
                    elif addon.addon_type == "UR":  # Unit Rate
                        addon_price_per_unit = addon.get_price(currency, service_level)
                        if addon_price_per_unit:
                            addon_price = addon_price_per_unit * total_units

                    addon_info = {
                        "id": addon.id,
                        "name": addon.name,
                        "description": addon.description,
                        "commercial_description": addon.commercial_description,
                        "addon_type": addon.get_addon_type_display(),
                        "price": addon_price,
                        "price_per_unit": addon_price_per_unit,  # Add per-unit price for frontend calculations
                    }

                    if addon.mandatory:
                        mandatory_addons.append(addon_info)
                        if addon_price:
                            addon_total += addon_price
                            sla_price += addon_price
                    else:
                        optional_addons.append(addon_info)

                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,
                        "storage_price": storage_price_data.get(currency, 0),
                        "ha_replica_min": appcat_price.ha_replica_min,
                        "ha_replica_max": appcat_price.ha_replica_max,
                        "variable_unit": appcat_price.variable_unit,
                        "units": units,
                        "total_units": total_units,
                        "mandatory_addons": mandatory_addons,
                        "optional_addons": optional_addons,
                    }
                )

    # 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
