"""
CloudWatch and Lambda cost optimization detectors.

This module contains detectors for:
- CloudWatch Logs retention optimization
- CloudWatch Logs with no metric filters (stored but never queried)
- Lambda over-provisioning detection
"""

from typing import Any, Dict, List

from stacksage.pricing import (
    PRICING_CLOUDWATCH_LOGS_GB_MONTH,
    estimate_lambda_monthly_cost,
)


def detect_cloudwatch_logs_retention(
    session, log_groups: List[Dict[str, Any]] = None
) -> List[Dict[str, Any]]:
    """Detect log groups with 'Never Expire' retention and recommend cost-saving policies."""
    findings = []
    if session is None:
        return findings

    try:
        # If log_groups not provided, fetch them
        if log_groups is None:
            logs_client = session.client("logs")
            log_groups = []
            paginator = logs_client.get_paginator("describe_log_groups")
            for page in paginator.paginate():
                log_groups.extend(page.get("logGroups", []))

        for lg in log_groups:
            log_group_name = lg.get("logGroupName", "")
            retention_days = lg.get("retentionInDays")
            stored_bytes = lg.get("storedBytes", 0)

            # Flag groups with no retention (never expire)
            if retention_days is None:
                # Calculate storage cost using pricing constant
                stored_gb = stored_bytes / (1024**3)
                monthly_cost = round(stored_gb * PRICING_CLOUDWATCH_LOGS_GB_MONTH, 2)

                # Conservative estimate: setting 90-day retention saves ~60% of cost
                estimated_savings = round(monthly_cost * 0.60, 2)

                findings.append(
                    {
                        "type": "cloudwatch_logs_retention",
                        "resource_type": "logs",
                        "id": log_group_name,
                        "stored_gb": round(stored_gb, 2),
                        "retention_days": "Never Expire",
                        "estimated_monthly_cost_usd": monthly_cost,
                        "estimated_monthly_savings_usd": estimated_savings,
                        "potential_savings": estimated_savings,
                        "confidence": 0.75,
                        "severity": "medium" if estimated_savings > 5 else "low",
                        "recommended_action": "set-retention-policy",
                        "explanation": f"Log group has 'Never Expire' retention ({stored_gb:.2f} GB stored). Consider 30/90/365-day retention to reduce storage costs.",
                        "evidence": {
                            "inventory": {
                                "log_group_name": log_group_name,
                                "retention_days": retention_days,
                                "stored_bytes": stored_bytes,
                            },
                            "estimation": {
                                "stored_gb": round(stored_gb, 2),
                                "monthly_cost_usd": monthly_cost,
                            },
                        },
                        "verification_commands": [
                            f"aws logs describe-log-groups --log-group-name-prefix {log_group_name}",
                        ],
                        "remediation_commands": [
                            f"aws logs put-retention-policy --log-group-name {log_group_name} --retention-in-days 90"
                        ],
                    }
                )
    except Exception:
        # Gracefully handle permissions issues
        pass

    return findings


def detect_overprovisioned_lambda(
    session,
    lambda_functions: List[Dict[str, Any]] = None,
    days: int = 14,
    budget=None,
    cw_avg_func=None,
) -> List[Dict[str, Any]]:
    """
    Detect Lambda functions with excess memory allocation using CloudWatch metrics.

    Args:
        session: Boto3 session for CloudWatch access
        lambda_functions: List of Lambda function dictionaries (auto-fetches if None)
        days: Lookback period in days (default: 14)
        budget: MetricBudget instance for query throttling
        cw_avg_func: CloudWatch average function (defaults to importing from analysis.py)
    """
    findings = []
    if session is None:
        return findings

    # Handle circular dependency by accepting cw_avg as parameter
    if cw_avg_func is None:
        from stacksage.analyzer.metrics import cw_avg as cw_avg_func

    daily_period = 86400

    try:
        # If functions not provided, fetch them
        if lambda_functions is None:
            lambda_client = session.client("lambda")
            lambda_functions = []
            paginator = lambda_client.get_paginator("list_functions")
            for page in paginator.paginate():
                lambda_functions.extend(page.get("Functions", []))

        for func in lambda_functions:
            func_name = func.get("FunctionName", "")
            if not func_name:
                continue

            memory_size = func.get("MemorySize", 128)
            timeout = func.get("Timeout", 3)
            region = func.get("Region", "us-east-1")

            cw_errors: List[str] = []
            cw_detail: Dict[str, Any] = {"metrics": []}

            # Query average duration and max memory used
            avg_duration = cw_avg_func(
                session,
                "AWS/Lambda",
                "Duration",
                [{"Name": "FunctionName", "Value": func_name}],
                days=days,
                region_name=region,
                budget=budget,
                errors=cw_errors,
                evidence=cw_detail,
            )

            invocations_avg = cw_avg_func(
                session,
                "AWS/Lambda",
                "Invocations",
                [{"Name": "FunctionName", "Value": func_name}],
                period=daily_period,
                days=days,
                statistic="Sum",
                region_name=region,
                budget=budget,
                errors=cw_errors,
                evidence=cw_detail,
            )

            # Skip if no metrics or budget exhausted
            if avg_duration is None:
                continue

            # If duration is <50% of timeout, function likely over-provisioned
            duration_utilization = (
                avg_duration / 1000
            ) / timeout  # Convert ms to seconds

            # Conservative estimate: if duration <30% of timeout AND memory >256MB
            if duration_utilization < 0.30 and memory_size > 256:
                # Estimate potential downsize: reduce memory by 25%
                recommended_memory = max(128, int(memory_size * 0.75))

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

                current_cost = estimate_lambda_monthly_cost(
                    memory_size, avg_duration, invocations_per_month, region
                )
                optimized_cost = estimate_lambda_monthly_cost(
                    recommended_memory, avg_duration, invocations_per_month, region
                )
                savings = round(current_cost - optimized_cost, 2)

                if savings > 1:  # Only report if savings > $1
                    invocations_note = (
                        f"Estimated using measured invocations (~{invocations_per_month:,}/mo)."
                        if invocations_source == "measured"
                        else "Estimated using assumed 1,000,000 invocations/mo."
                    )
                    findings.append(
                        {
                            "type": "overprovisioned_lambda",
                            "resource_type": "lambda",
                            "id": func_name,
                            "region": region,
                            "memory_size_mb": memory_size,
                            "avg_duration_ms": round(avg_duration, 2),
                            "timeout_seconds": timeout,
                            "duration_utilization_pct": round(
                                duration_utilization * 100, 2
                            ),
                            "recommended_memory_mb": recommended_memory,
                            "estimated_monthly_savings_usd": savings,
                            "potential_savings": savings,
                            "confidence": (
                                0.75 if invocations_source == "measured" else 0.70
                            ),
                            "severity": "low",
                            "recommended_action": "rightsize-lambda",
                            "explanation": (
                                f"Function uses {duration_utilization*100:.1f}% of timeout with {memory_size}MB. "
                                f"Consider reducing to {recommended_memory}MB for cost savings. {invocations_note}"
                            ),
                            "evidence": {
                                "inventory": {
                                    "memory_size_mb": memory_size,
                                    "timeout_seconds": timeout,
                                    "runtime": func.get("Runtime"),
                                },
                                "utilization": {
                                    "avg_duration_ms": round(avg_duration, 2),
                                    "duration_utilization_pct": round(
                                        duration_utilization * 100, 2
                                    ),
                                    "lookback_days": days,
                                    "invocations_avg_per_day": (
                                        round(invocations_avg, 2)
                                        if invocations_avg is not None
                                        else None
                                    ),
                                    "invocations_estimated_per_month": invocations_per_month,
                                    "invocations_source": invocations_source,
                                },
                                "estimation": {
                                    "recommended_memory_mb": recommended_memory,
                                    "invocations_estimated_per_month": invocations_per_month,
                                },
                            },
                            "verification_commands": [
                                f"aws lambda get-function-configuration --function-name {func_name} --region {region}",
                                f"aws cloudwatch get-metric-statistics --namespace AWS/Lambda --metric-name Duration --dimensions Name=FunctionName,Value={func_name} --start-time $(date -u -v-{days}d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 300 --statistics Average --region {region}",
                            ],
                            "remediation_commands": [
                                f"aws lambda update-function-configuration --function-name {func_name} --memory-size {recommended_memory}"
                            ],
                            "metrics_error": ";".join(cw_errors) if cw_errors else None,
                            "cw_metrics": cw_detail.get("metrics"),
                        }
                    )
    except Exception:
        # Gracefully handle permissions issues
        pass

    return findings


def detect_cloudwatch_logs_no_metric_filters(
    session,
    log_groups: List[Dict[str, Any]] = None,
    min_stored_gb: float = 1.0,
) -> List[Dict[str, Any]]:
    """
    Detect large CloudWatch log groups with no metric filters defined.

    A log group with significant stored bytes but zero metric filters has no
    active queries extracting value from it.  Combined with a Never-Expire
    retention setting this is the clearest signal that retention can be
    aggressively reduced with minimal operational risk.

    This detector is CloudWatch-gated (requires ``session``).  It calls
    ``DescribeMetricFilters`` per log group — a read-only, low-cost API call.

    Findings intentionally carry ``estimated_monthly_savings_usd=0`` to avoid
    double-counting with ``detect_cloudwatch_logs_retention``.  The finding
    acts as a corroborating signal: "this log group is large AND nobody is
    querying it -> safe to set aggressive retention".

    Args:
        session: Boto3 session for CloudWatch Logs access.
        log_groups: Pre-fetched log group list.  Fetched automatically if None.
        min_stored_gb: Only flag log groups storing at least this many GB.
            Default 1.0 GB keeps noise low.

    Returns:
        One finding per qualifying log group where metric filter count == 0.
    """
    findings = []
    if session is None:
        return findings

    try:
        logs_client = session.client("logs")

        if log_groups is None:
            log_groups = []
            paginator = logs_client.get_paginator("describe_log_groups")
            for page in paginator.paginate():
                log_groups.extend(page.get("logGroups", []))

        for lg in log_groups:
            log_group_name: str = lg.get("logGroupName", "")
            if not log_group_name:
                continue

            stored_bytes: int = lg.get("storedBytes", 0) or 0
            stored_gb: float = stored_bytes / (1024**3)

            if stored_gb < min_stored_gb:
                continue

            # Count metric filters for this log group
            filter_count = 0
            try:
                filter_paginator = logs_client.get_paginator("describe_metric_filters")
                for page in filter_paginator.paginate(logGroupName=log_group_name):
                    filter_count += len(page.get("metricFilters", []))
            except Exception:
                # AccessDenied or unsupported — skip this group
                continue

            if filter_count > 0:
                continue

            retention_days = lg.get("retentionInDays")
            has_no_retention = retention_days is None

            from stacksage.pricing import PRICING_CLOUDWATCH_LOGS_GB_MONTH

            monthly_cost = round(stored_gb * PRICING_CLOUDWATCH_LOGS_GB_MONTH, 2)
            severity = "medium" if monthly_cost > 10 else "low"

            findings.append(
                {
                    "type": "cloudwatch_logs_no_metric_filters",
                    "resource_type": "logs",
                    "id": log_group_name,
                    "stored_gb": round(stored_gb, 2),
                    "retention_days": (
                        retention_days if retention_days else "Never Expire"
                    ),
                    "metric_filter_count": 0,
                    "estimated_monthly_cost_usd": monthly_cost,
                    # Intentionally zero: savings counted by detect_cloudwatch_logs_retention
                    # to avoid double-counting.  This finding is a risk signal for safe
                    # retention reduction, not an independent savings estimate.
                    "estimated_monthly_savings_usd": 0,
                    "potential_savings": 0,
                    "confidence": 0.8,
                    "severity": severity,
                    "recommended_action": "review-and-set-retention-or-delete",
                    "explanation": (
                        f"Log group '{log_group_name}' stores {stored_gb:.2f} GB "
                        f"(~${monthly_cost:.2f}/mo) with zero metric filters. "
                        "No CloudWatch Metrics are derived from this log group, "
                        "indicating it is stored but never analysed. "
                        + (
                            "It also has no retention policy (Never Expire). "
                            if has_no_retention
                            else f"Current retention: {retention_days} days. "
                        )
                        + "Reducing retention aggressively carries low operational risk."
                    ),
                    "evidence": {
                        "inventory": {
                            "log_group_name": log_group_name,
                            "stored_bytes": stored_bytes,
                            "stored_gb": round(stored_gb, 2),
                            "retention_days": retention_days,
                            "metric_filter_count": 0,
                        },
                        "assessment": {
                            "has_retention_policy": not has_no_retention,
                            "has_metric_filters": False,
                            "monthly_storage_cost_usd": monthly_cost,
                            "min_stored_gb_threshold": min_stored_gb,
                        },
                    },
                    "verification_commands": [
                        f"aws logs describe-log-groups --log-group-name-prefix {log_group_name}",
                        f"aws logs describe-metric-filters --log-group-name {log_group_name}",
                    ],
                    "remediation_commands": [
                        f"aws logs put-retention-policy --log-group-name {log_group_name} --retention-in-days 30",
                        f"# Verify no active subscriptions before deleting: "
                        f"aws logs describe-subscription-filters --log-group-name {log_group_name}",
                    ],
                }
            )

    except Exception:
        pass

    return findings
