"""
Architecture optimization detectors: serverless, Graviton, modernization recommendations.

These detectors go beyond "waste detection" to suggest better, cheaper architectures.
"""

from typing import Any, Dict, List, Optional

from stacksage.pricing import (
    PRICING_API_GATEWAY_REST_REQUESTS,
    PRICING_EC2_HOURLY_ON_DEMAND,
    PRICING_FARGATE_GB_HOUR,
    PRICING_FARGATE_SPOT_DISCOUNT_PCT,
    PRICING_FARGATE_VCPU_HOUR,
    PRICING_LAMBDA_GB_SECOND,
    PRICING_LAMBDA_REQUEST_INVOCATIONS,
    PRICING_LAMBDA_REQUEST_MILLION,
    estimate_ec2_monthly_cost,
    estimate_rds_monthly_cost,
    get_regional_multiplier,
)

# Migration effort estimates
MIGRATION_EFFORT_LOW = "low"
MIGRATION_EFFORT_MEDIUM = "medium"
MIGRATION_EFFORT_HIGH = "high"


# Lambda Graviton guidance
LAMBDA_GRAVITON_SAVINGS_PCT = 0.20
LAMBDA_GRAVITON_MIN_SAVINGS_USD = 1.0


def detect_serverless_migration_opportunities(
    instances: List[Dict[str, Any]],
    cw_avg_fn=None,
    days: int = 30,
) -> List[Dict[str, Any]]:
    """
    Detect EC2 instances that could be migrated to serverless (Lambda + API Gateway).

    Detection Criteria:
    - Small instance types (t3.micro, t3.small, t3.medium, t4g.micro, t4g.small)
    - Low CPU utilization (<20% average)
    - Likely running HTTP/API services (security group port 80/443, IAM roles suggesting API access)
    - Running state (ignore stopped instances)

    Recommendation: Migrate to Lambda + API Gateway for pay-per-invoke pricing

    Args:
        instances: List of EC2 instance dictionaries
        cw_avg_fn: CloudWatch average function (keyword-based, like DynamoDB/ElastiCache detectors)
        days: CloudWatch lookback period (default 30)

    Returns:
        List of findings with serverless migration recommendations
    """
    findings = []

    # If no CloudWatch function, skip (need CPU metrics)
    if cw_avg_fn is None:
        return findings

    # Instance types suitable for serverless migration (small, always-on workloads)
    serverless_candidate_types = {
        "t3.micro",
        "t3.small",
        "t3.medium",
        "t4g.micro",
        "t4g.small",
        "t4g.medium",
        "t2.micro",
        "t2.small",
        "t2.medium",
    }

    for inst in instances:
        instance_type = inst.get("InstanceType", "")
        state = inst.get("State", "")
        instance_id = inst.get("InstanceId", "")
        region = inst.get("Region", "us-east-1")

        # Skip if not running or not a candidate type
        if state != "running":
            continue
        if instance_type not in serverless_candidate_types:
            continue

        # Check if instance has security group rules suggesting HTTP/API service
        # (This is a heuristic - in practice, we'd look at security group ingress rules)
        # For now, we'll flag all small instances with low CPU as candidates

        # Get CPU utilization from CloudWatch
        cpu_avg = None

        try:
            # Use dict format (like DynamoDB/ElastiCache detectors)
            # cw_avg will convert to list format internally
            cpu_avg = cw_avg_fn(
                namespace="AWS/EC2",
                metric="CPUUtilization",
                dimensions={"InstanceId": instance_id},
                stat="Average",
                lookback_days=days,
                region=region,
            )
        except Exception:
            # CloudWatch query failed, skip this instance
            continue
        # If we have CPU data and it's low (<20%), flag as candidate
        if cpu_avg is not None and cpu_avg < 20.0:
            # Calculate cost comparison
            current_monthly_cost = _estimate_ec2_monthly_cost(instance_type, region)

            # Estimate serverless cost (assuming moderate usage: 1M requests/month, 256MB, 500ms avg)
            estimated_monthly_requests = 1_000_000  # 1M requests/month
            estimated_lambda_memory_mb = 256
            estimated_lambda_duration_seconds = 0.5

            serverless_cost = _estimate_serverless_cost(
                estimated_monthly_requests,
                estimated_lambda_memory_mb,
                estimated_lambda_duration_seconds,
            )

            savings = current_monthly_cost - serverless_cost
            savings_percentage = (
                (savings / current_monthly_cost * 100)
                if current_monthly_cost > 0
                else 0
            )

            # Only flag if savings >$5/month (filter out noise)
            if savings >= 5.0:
                migration_effort = _assess_migration_effort(instance_type, cpu_avg)

                findings.append(
                    {
                        "type": "architecture_opportunity",
                        "resource_type": "ec2_instance",
                        "id": instance_id,
                        "region": region,
                        "instance_type": instance_type,
                        "estimated_monthly_cost_usd": round(current_monthly_cost, 2),
                        "estimated_monthly_savings_usd": round(savings, 2),
                        "potential_savings": round(savings, 2),
                        "confidence": 0.7,  # Medium confidence (heuristic-based)
                        "severity": "medium",
                        "recommended_action": "migrate-to-serverless",
                        "summary": f"EC2 instance running low-traffic service (CPU: {cpu_avg:.1f}%). Migrate to Lambda + API Gateway for {savings_percentage:.0f}% cost reduction.",
                        "explanation": (
                            f"This {instance_type} instance has low CPU utilization ({cpu_avg:.1f}%) and could be migrated "
                            f"to a serverless architecture (Lambda + API Gateway). Current cost: ${current_monthly_cost:.2f}/month. "
                            f"Estimated serverless cost: ${serverless_cost:.2f}/month (based on {estimated_monthly_requests:,} requests/month). "
                            f"Potential savings: ${savings:.2f}/month ({savings_percentage:.0f}%)."
                        ),
                        "evidence": {
                            "current_architecture": {
                                "type": "EC2 always-on",
                                "instance_type": instance_type,
                                "cpu_utilization_avg_pct": round(cpu_avg, 2),
                                "monthly_cost_usd": round(current_monthly_cost, 2),
                            },
                            "recommended_architecture": {
                                "type": "Serverless (Lambda + API Gateway)",
                                "lambda_memory_mb": estimated_lambda_memory_mb,
                                "lambda_duration_seconds": estimated_lambda_duration_seconds,
                                "estimated_monthly_requests": estimated_monthly_requests,
                                "monthly_cost_usd": round(serverless_cost, 2),
                            },
                            "migration": {
                                "effort": migration_effort,
                                "tools": ["AWS SAM", "Serverless Framework", "AWS CDK"],
                                "steps": [
                                    "1. Identify application code and dependencies",
                                    "2. Refactor into Lambda-compatible handler functions",
                                    "3. Configure API Gateway routes",
                                    "4. Set up Lambda environment variables and IAM role",
                                    "5. Test in non-production environment",
                                    "6. Migrate traffic gradually using weighted routing",
                                ],
                            },
                            "cloudwatch_metrics": {
                                "cpu_utilization_avg_pct": (
                                    round(cpu_avg, 2) if cpu_avg else None
                                ),
                                "lookback_days": days,
                            },
                        },
                        "verification_commands": [
                            f"aws ec2 describe-instances --instance-ids {instance_id}",
                            f"aws cloudwatch get-metric-statistics --namespace AWS/EC2 --metric-name CPUUtilization --dimensions Name=InstanceId,Value={instance_id} --start-time $(date -u -d '{days} days ago' +%Y-%m-%dT%H:%M:%S) --end-time $(date -u +%Y-%m-%dT%H:%M:%S) --period 86400 --statistics Average",
                        ],
                        "remediation_commands": [
                            "# Migration steps:",
                            "# 1. Install AWS SAM CLI:",
                            "#    brew install aws-sam-cli  # macOS",
                            "#    pip install aws-sam-cli   # Python",
                            "",
                            "# 2. Initialize SAM project:",
                            "#    sam init --runtime python3.12 --name my-serverless-app",
                            "",
                            "# 3. Refactor application code into Lambda handler",
                            "# 4. Define API Gateway routes in template.yaml",
                            "# 5. Deploy:",
                            "#    sam build && sam deploy --guided",
                            "",
                            "# Alternative: Use Serverless Framework",
                            "#    npm install -g serverless",
                            "#    serverless create --template aws-python3",
                            "#    serverless deploy",
                        ],
                    }
                )

    return findings


def detect_lambda_graviton_migration_opportunities(
    session,
    lambda_functions: Optional[List[Dict[str, Any]]] = None,
    regions: Optional[List[str]] = None,
    days: int = 14,
    budget=None,
    cw_avg_func=None,
) -> List[Dict[str, Any]]:
    """Recommend migrating Lambda functions from x86_64 to arm64 (Graviton).

    Heuristics:
    - Only consider functions currently on x86_64
    - Only for runtimes that generally support arm64
    - Prefer Zip package type (container images vary; can be multi-arch)
        - Uses CloudWatch Duration + Invocations to estimate compute spend, then applies an
            ≈20% compute savings estimate for arm64.

    Notes:
    - Confidence is reduced when Layers are present (native deps more likely).
    - If metrics are unavailable, the function is skipped (to avoid noisy $0 findings).
    """
    findings: List[Dict[str, Any]] = []
    if session is None:
        return findings

    if cw_avg_func is None:
        # Import lazily to avoid circular imports during module init.
        from stacksage.analyzer.metrics import cw_avg as cw_avg_func

    target_regions = regions or []
    if not target_regions:
        # Fall back to session default region if caller didn't provide a regions list.
        default_region = getattr(session, "region_name", None) or "us-east-1"
        target_regions = [default_region]

    # If functions not provided, fetch them (per-region)
    if lambda_functions is None:
        lambda_functions = []
        for reg in target_regions:
            try:
                lambda_client = session.client("lambda", region_name=reg)
                paginator = lambda_client.get_paginator("list_functions")
                for page in paginator.paginate():
                    for fn in page.get("Functions", []) or []:
                        if isinstance(fn, dict):
                            fn = dict(fn)
                            fn["Region"] = reg
                            lambda_functions.append(fn)
            except Exception:
                # Permission/region issues should not fail the whole run.
                continue

    supported = _lambda_runtime_supports_arm64

    daily_period = 86400
    for fn in lambda_functions or []:
        name = fn.get("FunctionName")
        if not name:
            continue

        region = (
            fn.get("Region") or getattr(session, "region_name", None) or "us-east-1"
        )
        runtime = fn.get("Runtime") or ""
        pkg_type = fn.get("PackageType") or "Zip"
        architectures = fn.get("Architectures") or ["x86_64"]
        layers = fn.get("Layers") or []

        if "arm64" in architectures:
            continue
        if pkg_type != "Zip":
            # Container images can be migrated too, but need multi-arch images; skip for now.
            continue
        if not supported(runtime):
            continue

        memory_mb = int(fn.get("MemorySize") or 128)

        # Fetch CloudWatch metrics
        try:
            avg_duration_ms = cw_avg_func(
                session,
                "AWS/Lambda",
                "Duration",
                [{"Name": "FunctionName", "Value": name}],
                days=days,
                region_name=region,
                budget=budget,
            )
            invocations_daily = cw_avg_func(
                session,
                "AWS/Lambda",
                "Invocations",
                [{"Name": "FunctionName", "Value": name}],
                period=daily_period,
                days=days,
                statistic="Sum",
                region_name=region,
                budget=budget,
            )
        except Exception:
            continue

        if avg_duration_ms is None:
            continue

        invocations_per_month: int
        invocations_source = "assumed"
        if invocations_daily is not None:
            invocations_per_month = max(0, int(invocations_daily * 30))
            invocations_source = "measured"
        else:
            invocations_per_month = 1_000_000

        # Estimate current cost (compute + requests), then apply 20% compute savings.
        regional_multiplier = get_regional_multiplier(region)
        memory_gb = memory_mb / 1024
        duration_seconds = float(avg_duration_ms) / 1000
        gb_seconds = memory_gb * duration_seconds * invocations_per_month
        compute_cost = gb_seconds * PRICING_LAMBDA_GB_SECOND * regional_multiplier
        request_cost = (
            (invocations_per_month / 1_000_000)
            * PRICING_LAMBDA_REQUEST_MILLION
            * regional_multiplier
        )

        current_cost = round(compute_cost + request_cost, 2)
        optimized_cost = round(
            (compute_cost * (1.0 - LAMBDA_GRAVITON_SAVINGS_PCT)) + request_cost, 2
        )
        savings = round(current_cost - optimized_cost, 2)

        if savings < LAMBDA_GRAVITON_MIN_SAVINGS_USD:
            continue

        confidence = 0.85 if not layers else 0.70
        severity = "medium" if savings >= 10 else "low"
        layers_note = (
            "Function uses Lambda Layers; validate any native dependencies on arm64."
            if layers
            else "No Layers detected; arm64 migration is typically straightforward."
        )

        findings.append(
            {
                "type": "lambda_graviton_migration",
                "resource_type": "lambda",
                "id": name,
                "region": region,
                "severity": severity,
                "confidence": confidence,
                "recommended_action": "migrate-lambda-to-arm64",
                "estimated_monthly_cost_usd": current_cost,
                "estimated_monthly_savings_usd": savings,
                "potential_savings": savings,
                "summary": (
                    f"Lambda '{name}' is running on x86_64. Consider arm64 (Graviton) for ~20% compute savings "
                    f"(~${savings:.2f}/mo)."
                ),
                "explanation": (
                    f"Runtime '{runtime}' supports arm64. Estimated current monthly cost is ${current_cost:.2f} "
                    f"(invocations: {invocations_source}, avg duration: {avg_duration_ms:.1f}ms, memory: {memory_mb}MB). "
                    f"Migrating to arm64 typically reduces compute cost by ~20%. {layers_note}"
                ),
                "evidence": {
                    "inventory": {
                        "runtime": runtime,
                        "package_type": pkg_type,
                        "architectures": architectures,
                        "memory_mb": memory_mb,
                        "layers_count": len(layers) if isinstance(layers, list) else 0,
                    },
                    "cloudwatch": {
                        "lookback_days": days,
                        "avg_duration_ms": round(float(avg_duration_ms), 2),
                        "invocations_daily": (
                            round(float(invocations_daily), 2)
                            if invocations_daily is not None
                            else None
                        ),
                        "invocations_per_month": invocations_per_month,
                        "invocations_source": invocations_source,
                    },
                    "estimation": {
                        "compute_cost_usd": round(compute_cost, 2),
                        "request_cost_usd": round(request_cost, 2),
                        "estimated_arm64_compute_savings_pct": int(
                            LAMBDA_GRAVITON_SAVINGS_PCT * 100
                        ),
                        "estimated_monthly_cost_usd": current_cost,
                        "estimated_monthly_savings_usd": savings,
                        "estimated_monthly_cost_arm64_usd": optimized_cost,
                    },
                },
                "verification_commands": [
                    f"aws lambda get-function-configuration --function-name {name} --region {region}",
                    f"aws cloudwatch get-metric-statistics --namespace AWS/Lambda --metric-name Duration --dimensions Name=FunctionName,Value={name} --period 86400 --statistics Average --region {region}",
                ],
                "remediation_commands": [
                    "# Validate dependencies and run tests on arm64 before production cutover.",
                    f"aws lambda update-function-configuration --function-name {name} --architectures arm64 --region {region}",
                ],
            }
        )

    return findings


def _lambda_runtime_supports_arm64(runtime: str) -> bool:
    """Conservative check for Lambda runtimes that generally support arm64."""
    if not runtime:
        return False

    # Common managed runtimes with arm64 support.
    allowed_prefixes = (
        "python3.",
        "nodejs",
        "java",
        "provided.al2",
        "dotnet",
        "ruby",
        "go",
    )
    if not runtime.startswith(allowed_prefixes):
        return False

    # Exclude clearly legacy runtimes that are typically problematic/unsupported.
    legacy = (
        "nodejs12.",
        "nodejs10.",
        "python2.",
        "python3.6",
        "python3.7",
        "dotnetcore",
    )
    if runtime.startswith(legacy):
        return False

    return True


def detect_rds_to_aurora_serverless_v2(
    session,
    rds_list: List[Dict[str, Any]],
    days: int = 14,
    budget=None,
    cw_avg_func=None,
) -> List[Dict[str, Any]]:
    """Detect spiky RDS workloads that may be better suited for Aurora Serverless v2.

    Heuristic:
    - Engine mysql/postgres (not already Aurora)
    - Status available
    - CPUUtilization shows spiky behavior (Maximum high, Average low)

    Savings:
    - Uses current RDS instance monthly estimate (instance-only) and applies a conservative
      savings factor when the workload is clearly spiky.
    """
    findings: List[Dict[str, Any]] = []
    if session is None:
        return findings
    if not rds_list:
        return findings

    if cw_avg_func is None:
        from stacksage.analyzer.metrics import cw_avg as cw_avg_func

    for db in rds_list:
        dbid = db.get("DBInstanceIdentifier") or db.get("id")
        if not dbid:
            continue
        engine = (db.get("Engine") or "").lower()
        status = (db.get("DBInstanceStatus") or "").lower()
        instance_class = db.get("DBInstanceClass") or ""
        region = (
            db.get("Region")
            or db.get("region")
            or getattr(session, "region_name", None)
            or "us-east-1"
        )

        if status and status != "available":
            continue
        if engine not in {"mysql", "postgres", "postgresql"}:
            continue
        # Skip Aurora engines (already serverless-capable path)
        if engine.startswith("aurora"):
            continue

        dims = [{"Name": "DBInstanceIdentifier", "Value": dbid}]

        try:
            cpu_avg = cw_avg_func(
                session,
                "AWS/RDS",
                "CPUUtilization",
                dims,
                days=days,
                statistic="Average",
                region_name=region,
                budget=budget,
            )
            cpu_max = cw_avg_func(
                session,
                "AWS/RDS",
                "CPUUtilization",
                dims,
                days=days,
                statistic="Maximum",
                region_name=region,
                budget=budget,
            )
        except Exception:
            continue

        if cpu_avg is None or cpu_max is None:
            continue

        cpu_avg_f = float(cpu_avg)
        cpu_max_f = float(cpu_max)
        spike_ratio = cpu_max_f / max(0.1, cpu_avg_f)

        is_spiky = cpu_max_f >= 60.0 and cpu_avg_f <= 20.0 and spike_ratio >= 4.0
        if not is_spiky:
            continue

        current_cost = estimate_rds_monthly_cost(instance_class, region=region)
        # Conservative: 30% savings for clearly spiky workloads.
        est_savings = round(current_cost * 0.30, 2)
        if est_savings < 5.0:
            continue

        severity = "medium" if est_savings >= 25 else "low"
        findings.append(
            {
                "type": "rds_to_aurora_serverless_v2",
                "resource_type": "rds",
                "id": dbid,
                "region": region,
                "severity": severity,
                "confidence": 0.7,
                "recommended_action": "evaluate-aurora-serverless-v2",
                "estimated_monthly_cost_usd": current_cost,
                "estimated_monthly_savings_usd": est_savings,
                "potential_savings": est_savings,
                "summary": (
                    f"RDS instance '{dbid}' appears spiky (CPU avg {cpu_avg_f:.1f}%, max {cpu_max_f:.1f}%). "
                    "Evaluate Aurora Serverless v2 to reduce always-on costs."
                ),
                "explanation": (
                    f"Over the last {days} days, CPUUtilization shows a spiky pattern (avg {cpu_avg_f:.1f}%, "
                    f"max {cpu_max_f:.1f}%, ratio {spike_ratio:.1f}x). Aurora Serverless v2 can scale capacity "
                    "to match demand, potentially reducing cost vs an always-on RDS instance."
                ),
                "evidence": {
                    "inventory": {
                        "engine": engine,
                        "instance_class": instance_class,
                        "status": status,
                    },
                    "cloudwatch": {
                        "lookback_days": days,
                        "cpu_utilization_avg_pct": round(cpu_avg_f, 2),
                        "cpu_utilization_max_pct": round(cpu_max_f, 2),
                        "spike_ratio": round(spike_ratio, 2),
                    },
                    "estimation": {
                        "current_monthly_cost_usd": current_cost,
                        "assumed_savings_pct": 30,
                        "estimated_monthly_savings_usd": est_savings,
                    },
                },
                "verification_commands": [
                    f"aws rds describe-db-instances --db-instance-identifier {dbid} --region {region}",
                    f"aws cloudwatch get-metric-statistics --namespace AWS/RDS --metric-name CPUUtilization --dimensions Name=DBInstanceIdentifier,Value={dbid} --period 86400 --statistics Average Maximum --region {region}",
                ],
                "remediation_commands": [
                    "# Plan and test carefully; migrating databases requires downtime or replication cutover.",
                    "# 1) Review Aurora Serverless v2 compatibility for your engine version.",
                    "# 2) Create an Aurora cluster and replicate (DMS / native replication) before cutover.",
                ],
            }
        )

    return findings


def detect_ecs_to_fargate_migration_opportunities(
    session,
    ecs_services: Optional[List[Dict[str, Any]]] = None,
    days: int = 14,
    budget=None,
    cw_avg_func=None,
) -> List[Dict[str, Any]]:
    """Detect ECS services on EC2 that are good candidates for AWS Fargate.

    Heuristics:
    - Service is EC2-backed (LaunchType=EC2 or capacity provider not FARGATE/FARGATE_SPOT)
    - DesiredCount > 0
    - Low utilization (avg CPU & Memory < 20%) based on AWS/ECS service metrics
    - Task resources (TaskCpuUnits/TaskMemoryMiB) present (from scanner enrichment)

    Cost model:
    - Current cost estimated as the minimum always-on EC2 host size required to run
      the service's desired tasks (very rough; may over/under-estimate in shared clusters).
    - Fargate cost estimated from task vCPU/GB and desired task count.
    """
    findings: List[Dict[str, Any]] = []
    if session is None:
        return findings
    if not ecs_services:
        return findings

    if cw_avg_func is None:
        from stacksage.analyzer.metrics import cw_avg as cw_avg_func

    for svc in ecs_services or []:
        if not isinstance(svc, dict):
            continue

        region = svc.get("Region") or svc.get("region") or "us-east-1"
        desired = svc.get("DesiredCount")
        try:
            desired_i = int(desired or 0)
        except Exception:
            desired_i = 0
        if desired_i <= 0:
            continue

        launch_type = (svc.get("LaunchType") or "").upper()
        cps = svc.get("CapacityProviderStrategy") or []
        cp_names = []
        for c in cps:
            if isinstance(c, dict) and c.get("capacityProvider"):
                cp_names.append(str(c.get("capacityProvider")))
            elif isinstance(c, dict) and c.get("CapacityProvider"):
                cp_names.append(str(c.get("CapacityProvider")))
        is_fargate_backed = launch_type == "FARGATE" or any(
            n.upper().startswith("FARGATE") for n in cp_names
        )
        is_ec2_backed = launch_type == "EC2" or (
            not is_fargate_backed
            and ((launch_type == "" and bool(cp_names)) or launch_type in ("EC2", ""))
        )
        if not is_ec2_backed or is_fargate_backed:
            continue

        cluster = svc.get("ClusterName")
        service_name = svc.get("ServiceName")
        service_arn = svc.get("ServiceArn")
        if not cluster or not service_name:
            continue

        vcpu, mem_gb = _ecs_task_vcpu_mem_gb(svc)
        if vcpu is None or mem_gb is None:
            continue

        cw_errors: List[str] = []
        dims = {"ClusterName": str(cluster), "ServiceName": str(service_name)}
        cpu_avg = cw_avg_func(
            session,
            "AWS/ECS",
            "CPUUtilization",
            dims,
            days=days,
            region_name=region,
            budget=budget,
            errors=cw_errors,
        )
        mem_avg = cw_avg_func(
            session,
            "AWS/ECS",
            "MemoryUtilization",
            dims,
            days=days,
            region_name=region,
            budget=budget,
            errors=cw_errors,
        )

        if cpu_avg is None or mem_avg is None:
            continue

        cpu_f = float(cpu_avg)
        mem_f = float(mem_avg)
        if cpu_f >= 20.0 or mem_f >= 20.0:
            continue

        current_ec2_cost = _estimate_ecs_ec2_host_monthly_cost(
            vcpu_total=vcpu * desired_i,
            mem_gb_total=mem_gb * desired_i,
            region=region,
        )
        fargate_cost = _estimate_fargate_monthly_cost(
            vcpu=vcpu,
            mem_gb=mem_gb,
            tasks=desired_i,
            region=region,
            spot=False,
        )
        est_savings = round(current_ec2_cost - fargate_cost, 2)
        if est_savings < 5.0:
            continue

        severity = "medium" if est_savings >= 25 else "low"
        findings.append(
            {
                "type": "ecs_to_fargate",
                "resource_type": "ecs",
                "id": service_arn or f"{cluster}:{service_name}",
                "region": region,
                "severity": severity,
                "confidence": 0.65,
                "recommended_action": "migrate-ecs-to-fargate",
                "estimated_monthly_cost_usd": round(current_ec2_cost, 2),
                "estimated_monthly_savings_usd": est_savings,
                "potential_savings": est_savings,
                "summary": (
                    f"ECS service '{service_name}' in cluster '{cluster}' is low-utilization "
                    f"(CPU {cpu_f:.1f}%, Memory {mem_f:.1f}%). Consider moving from EC2 to Fargate."
                ),
                "explanation": (
                    f"Over the last {days} days, service-level CPUUtilization averaged {cpu_f:.1f}% and "
                    f"MemoryUtilization averaged {mem_f:.1f}%. For small, steady services, Fargate can reduce "
                    "always-on EC2 host overhead and simplify operations."
                ),
                "evidence": {
                    "inventory": {
                        "cluster": cluster,
                        "service": service_name,
                        "desired_count": desired_i,
                        "launch_type": launch_type or None,
                        "capacity_providers": cp_names,
                        "task_cpu_units": svc.get("TaskCpuUnits"),
                        "task_memory_mib": svc.get("TaskMemoryMiB"),
                    },
                    "cloudwatch": {
                        "lookback_days": days,
                        "cpu_utilization_avg_pct": round(cpu_f, 2),
                        "memory_utilization_avg_pct": round(mem_f, 2),
                        "errors": cw_errors,
                    },
                    "estimation": {
                        "current_ec2_monthly_cost_usd": round(current_ec2_cost, 2),
                        "estimated_fargate_monthly_cost_usd": round(fargate_cost, 2),
                        "estimated_monthly_savings_usd": est_savings,
                    },
                },
                "verification_commands": [
                    f"aws ecs describe-services --cluster {cluster} --services {service_name} --region {region}",
                    f"aws cloudwatch get-metric-statistics --namespace AWS/ECS --metric-name CPUUtilization --dimensions Name=ClusterName,Value={cluster} Name=ServiceName,Value={service_name} --period 3600 --statistics Average --region {region}",
                ],
                "remediation_commands": [
                    "# High-level steps (test carefully in non-prod):",
                    "# 1) Ensure task definition is Fargate-compatible (awsvpc networking, CPU/memory set).",
                    "# 2) Create or update service with capacity provider FARGATE (or LaunchType=FARGATE).",
                    "# 3) Deploy with rolling update and validate health checks.",
                ],
            }
        )

    return findings


def detect_fargate_spot_opportunities(
    session,
    ecs_services: Optional[List[Dict[str, Any]]] = None,
    min_savings_usd: float = 5.0,
) -> List[Dict[str, Any]]:
    """Detect ECS services on Fargate that may be good candidates for Fargate Spot.

    Heuristics:
    - Service is Fargate-backed
    - Not already using FARGATE_SPOT
    - Non-production indicator via tags or service name

    Cost model:
    - Applies a rough discount (PRICING_FARGATE_SPOT_DISCOUNT_PCT) to on-demand
      compute cost based on task vCPU/GB and desired task count.
    """
    findings: List[Dict[str, Any]] = []
    if session is None:
        return findings
    if not ecs_services:
        return findings

    for svc in ecs_services or []:
        if not isinstance(svc, dict):
            continue

        region = svc.get("Region") or svc.get("region") or "us-east-1"
        desired = svc.get("DesiredCount")
        try:
            desired_i = int(desired or 0)
        except Exception:
            desired_i = 0
        if desired_i <= 0:
            continue

        launch_type = (svc.get("LaunchType") or "").upper()
        cps = svc.get("CapacityProviderStrategy") or []
        cp_names = []
        for c in cps:
            if isinstance(c, dict) and c.get("capacityProvider"):
                cp_names.append(str(c.get("capacityProvider")))
            elif isinstance(c, dict) and c.get("CapacityProvider"):
                cp_names.append(str(c.get("CapacityProvider")))
        is_fargate_backed = launch_type == "FARGATE" or any(
            n.upper().startswith("FARGATE") for n in cp_names
        )
        if not is_fargate_backed:
            continue

        already_spot = any(n.upper() == "FARGATE_SPOT" for n in cp_names)
        if already_spot:
            continue

        cluster = svc.get("ClusterName")
        service_name = svc.get("ServiceName")
        service_arn = svc.get("ServiceArn")
        if not cluster or not service_name:
            continue

        tags = svc.get("Tags") or svc.get("tags") or []
        env = None
        for t in tags or []:
            if isinstance(t, dict) and str(t.get("Key", "")).lower() in (
                "env",
                "environment",
                "stage",
            ):
                env = str(t.get("Value", "")).lower()
                break
        name_l = str(service_name).lower()
        nonprod = bool(
            (
                env
                and env
                in (
                    "dev",
                    "development",
                    "test",
                    "qa",
                    "staging",
                    "stage",
                    "nonprod",
                )
            )
            or any(
                tok in name_l
                for tok in ("-dev", "-test", "-qa", "-staging", "-stage", "nonprod")
            )
        )
        if not nonprod:
            continue

        vcpu, mem_gb = _ecs_task_vcpu_mem_gb(svc)
        if vcpu is None or mem_gb is None:
            continue

        on_demand_cost = _estimate_fargate_monthly_cost(
            vcpu=vcpu,
            mem_gb=mem_gb,
            tasks=desired_i,
            region=region,
            spot=False,
        )
        spot_cost = _estimate_fargate_monthly_cost(
            vcpu=vcpu,
            mem_gb=mem_gb,
            tasks=desired_i,
            region=region,
            spot=True,
        )
        est_savings = round(on_demand_cost - spot_cost, 2)
        if est_savings < float(min_savings_usd):
            continue

        severity = "medium" if est_savings >= 25 else "low"
        confidence = 0.75 if env else 0.6
        findings.append(
            {
                "type": "fargate_spot_opportunity",
                "resource_type": "ecs",
                "id": service_arn or f"{cluster}:{service_name}",
                "region": region,
                "severity": severity,
                "confidence": confidence,
                "recommended_action": "use-fargate-spot",
                "estimated_monthly_cost_usd": round(on_demand_cost, 2),
                "estimated_monthly_savings_usd": est_savings,
                "potential_savings": est_savings,
                "summary": (
                    f"Fargate service '{service_name}' appears non-prod. Consider Fargate Spot for ~{int(PRICING_FARGATE_SPOT_DISCOUNT_PCT*100)}% compute savings."
                ),
                "explanation": (
                    "For non-critical workloads (dev/test/staging), Fargate Spot can significantly reduce compute cost, "
                    "with the trade-off of possible interruptions. Use capacity providers + graceful draining."
                ),
                "evidence": {
                    "inventory": {
                        "cluster": cluster,
                        "service": service_name,
                        "desired_count": desired_i,
                        "launch_type": launch_type or None,
                        "capacity_providers": cp_names,
                        "tags": tags,
                    },
                    "estimation": {
                        "on_demand_monthly_cost_usd": round(on_demand_cost, 2),
                        "spot_monthly_cost_usd": round(spot_cost, 2),
                        "assumed_spot_discount_pct": int(
                            PRICING_FARGATE_SPOT_DISCOUNT_PCT * 100
                        ),
                        "estimated_monthly_savings_usd": est_savings,
                    },
                },
                "verification_commands": [
                    f"aws ecs describe-services --cluster {cluster} --services {service_name} --include TAGS --region {region}",
                ],
                "remediation_commands": [
                    "# High-level steps:",
                    "# 1) Create capacity provider strategy with FARGATE_SPOT + FARGATE fallback.",
                    "# 2) Update service to use capacity provider strategy.",
                ],
            }
        )

    return findings


def _ecs_task_vcpu_mem_gb(
    svc: Dict[str, Any],
) -> tuple[Optional[float], Optional[float]]:
    """Return (vcpu, memory_gb) parsed from scanner-enriched service record."""
    cpu_units = svc.get("TaskCpuUnits")
    mem_mib = svc.get("TaskMemoryMiB")
    try:
        cpu_units_i = int(cpu_units) if cpu_units is not None else None
    except Exception:
        cpu_units_i = None
    try:
        mem_mib_i = int(mem_mib) if mem_mib is not None else None
    except Exception:
        mem_mib_i = None
    if cpu_units_i is None or mem_mib_i is None:
        return (None, None)
    if cpu_units_i <= 0 or mem_mib_i <= 0:
        return (None, None)
    vcpu = cpu_units_i / 1024.0
    mem_gb = mem_mib_i / 1024.0
    return (vcpu, mem_gb)


def _estimate_fargate_monthly_cost(
    vcpu: float,
    mem_gb: float,
    tasks: int,
    region: str,
    spot: bool = False,
) -> float:
    if tasks <= 0:
        return 0.0
    mult = get_regional_multiplier(region)
    hourly_per_task = (vcpu * PRICING_FARGATE_VCPU_HOUR) + (
        mem_gb * PRICING_FARGATE_GB_HOUR
    )
    hourly = hourly_per_task * max(0, int(tasks))
    monthly = hourly * 730 * mult
    if spot:
        monthly = monthly * (1.0 - float(PRICING_FARGATE_SPOT_DISCOUNT_PCT))
    return float(monthly)


def _estimate_ecs_ec2_host_monthly_cost(
    vcpu_total: float,
    mem_gb_total: float,
    region: str,
) -> float:
    """Very rough estimate of minimum EC2 capacity to host a service.

    Assumption: at least a t3.small host is required for ECS agent + OS overhead.
    """
    # (instance_type, vcpu, mem_gb)
    candidates = [
        ("t3.small", 2.0, 2.0),
        ("t3.medium", 2.0, 4.0),
        ("t3.large", 2.0, 8.0),
        ("m5.large", 2.0, 8.0),
        ("m5.xlarge", 4.0, 16.0),
    ]

    chosen = "m5.large"
    for itype, vcpu, mem in candidates:
        if vcpu_total <= vcpu and mem_gb_total <= mem:
            chosen = itype
            break

    try:
        return float(estimate_ec2_monthly_cost(chosen, region=region))
    except Exception:
        # Fallback to local helper if pricing helper unavailable
        return float(_estimate_ec2_monthly_cost(chosen, region))


def _estimate_ec2_monthly_cost(instance_type: str, region: str) -> float:
    """
    Estimate monthly cost for EC2 instance (24/7 on-demand).

    Args:
        instance_type: EC2 instance type (e.g., "t3.small")
        region: AWS region

    Returns:
        Estimated monthly cost in USD
    """
    # Get hourly rate from pricing module
    hourly_rate = PRICING_EC2_HOURLY_ON_DEMAND.get(
        instance_type, 0.02
    )  # Default fallback

    # 730 hours/month (24 * 365.25 / 12)
    monthly_cost = hourly_rate * 730

    return monthly_cost


def _estimate_serverless_cost(
    monthly_requests: int,
    lambda_memory_mb: int,
    lambda_duration_seconds: float,
) -> float:
    """
    Estimate monthly cost for Lambda + API Gateway.

    Args:
        monthly_requests: Number of requests per month
        lambda_memory_mb: Lambda memory allocation in MB
        lambda_duration_seconds: Average Lambda duration in seconds

    Returns:
        Estimated monthly cost in USD
    """
    # Lambda costs
    # Free tier: 1M requests/month, 400,000 GB-seconds/month (ignore for simplicity)
    lambda_requests_cost = (
        monthly_requests / 1_000_000
    ) * PRICING_LAMBDA_REQUEST_INVOCATIONS

    # GB-seconds calculation
    lambda_gb_seconds = (
        (lambda_memory_mb / 1024) * lambda_duration_seconds * monthly_requests
    )
    lambda_compute_cost = lambda_gb_seconds * PRICING_LAMBDA_GB_SECOND

    # API Gateway costs
    api_gateway_cost = (
        monthly_requests / 1_000_000
    ) * PRICING_API_GATEWAY_REST_REQUESTS

    total_cost = lambda_requests_cost + lambda_compute_cost + api_gateway_cost

    return total_cost


def _assess_migration_effort(instance_type: str, cpu_avg: float) -> str:
    """
    Assess migration effort based on instance characteristics.

    Args:
        instance_type: EC2 instance type
        cpu_avg: Average CPU utilization percentage

    Returns:
        Migration effort level: "low", "medium", or "high"
    """
    # Heuristic: smaller instances with very low CPU = easier migration
    if cpu_avg < 10.0 and instance_type in ["t3.micro", "t4g.micro", "t2.micro"]:
        return MIGRATION_EFFORT_LOW
    elif cpu_avg < 15.0:
        return MIGRATION_EFFORT_MEDIUM
    else:
        return MIGRATION_EFFORT_HIGH
