"""ElastiCache waste detection for StackSage."""

from typing import Any, Dict, List, Optional

# Detection thresholds (configurable in future)
IDLE_HIT_RATE_THRESHOLD = 5.0  # Less than 5% cache hit rate
IDLE_CONNECTIONS_THRESHOLD = 2.0  # Less than 2 concurrent connections
MIN_REQUESTS_PER_DAY = 100.0  # Less than 100 cache requests/day


def detect_idle_elasticache_clusters(
    inventory: Dict[str, Any],
    cw_avg_fn: Any,
    pricing: Any,
    config: Any,
) -> List[Dict[str, Any]]:
    """Detect ElastiCache clusters with low cache hits and connections for 14+ days.

    Detection logic:
    - Query CloudWatch CacheHits, CacheMisses, and CurrConnections metrics
    - Calculate hit rate: CacheHits / (CacheHits + CacheMisses)
    - If hit rate <5% AND connections <2 for 14 days, flag as idle
    - Works for both Redis and Memcached

    Evidence:
    - CloudWatch metrics (14-day average)
    - Cluster configuration (engine, node type, node count)
    - Estimated monthly cost based on instance hours
    """
    findings = []

    # Process both Redis and Memcached clusters
    redis_clusters = inventory.get("elasticache_redis_clusters") or []
    memcached_clusters = inventory.get("elasticache_memcached_clusters") or []

    all_clusters = [
        *[{**c, "_engine": "redis"} for c in redis_clusters],
        *[{**c, "_engine": "memcached"} for c in memcached_clusters],
    ]

    for cluster in all_clusters:
        cluster_id = cluster.get("CacheClusterId") or cluster.get("ReplicationGroupId")
        engine = cluster.get("_engine")
        region = _extract_region_from_arn(cluster.get("ARN", ""))
        node_type = cluster.get("CacheNodeType", "")
        num_nodes = cluster.get("NumCacheNodes", 1)

        if not cluster_id:
            continue

        # For Redis replication groups, use ReplicationGroupId as dimension
        # For standalone clusters, use CacheClusterId
        dimension_name = (
            "ReplicationGroupId"
            if "ReplicationGroupId" in cluster
            else "CacheClusterId"
        )

        # Query CloudWatch for cache activity (14-day lookback)
        cache_hits_avg = cw_avg_fn(
            namespace="AWS/ElastiCache",
            metric="CacheHits",
            dimensions={dimension_name: cluster_id},
            stat="Average",
            lookback_days=14,
            region=region,
        )

        cache_misses_avg = cw_avg_fn(
            namespace="AWS/ElastiCache",
            metric="CacheMisses",
            dimensions={dimension_name: cluster_id},
            stat="Average",
            lookback_days=14,
            region=region,
        )

        curr_connections_avg = cw_avg_fn(
            namespace="AWS/ElastiCache",
            metric="CurrConnections",
            dimensions={dimension_name: cluster_id},
            stat="Average",
            lookback_days=14,
            region=region,
        )

        # Check if metrics are unavailable
        if (
            cache_hits_avg is None
            or cache_misses_avg is None
            or curr_connections_avg is None
        ):
            continue

        # Calculate hit rate (avoid division by zero)
        total_requests = cache_hits_avg + cache_misses_avg
        hit_rate = (
            (cache_hits_avg / total_requests * 100) if total_requests > 0 else 0.0
        )

        # Thresholds (using constants for maintainability):
        # - Hit rate < 5% (very low cache effectiveness)
        # - Connections < 2 (almost no active connections)
        # - Total requests < 100/day on average (minimal activity)
        is_idle = (
            hit_rate < IDLE_HIT_RATE_THRESHOLD
            and curr_connections_avg < IDLE_CONNECTIONS_THRESHOLD
            and total_requests < MIN_REQUESTS_PER_DAY
        )

        if not is_idle:
            continue

        # Calculate monthly cost
        monthly_cost = _estimate_elasticache_cost(node_type, num_nodes, region, pricing)

        # Build evidence
        evidence = {
            "cloudwatch": {
                "lookback_days": 14,
                "cache_hits_avg": round(cache_hits_avg, 2),
                "cache_misses_avg": round(cache_misses_avg, 2),
                "hit_rate_percent": round(hit_rate, 2),
                "connections_avg": round(curr_connections_avg, 2),
                "thresholds": {
                    "hit_rate_percent": 5.0,
                    "connections": 2.0,
                    "requests_per_day": 100.0,
                },
            },
            "cluster": {
                "id": cluster_id,
                "engine": engine,
                "node_type": node_type,
                "num_nodes": num_nodes,
            },
        }

        findings.append(
            {
                "type": "elasticache_idle_cluster",
                "resource_type": "elasticache_cluster",
                "id": cluster_id,
                "region": region,
                "severity": "medium",
                "confidence": 0.9,
                "estimated_monthly_savings_usd": monthly_cost,
                "estimated_monthly_cost_usd": monthly_cost,
                "recommended_action": "delete-elasticache-cluster",
                "summary": (
                    f"ElastiCache {engine.capitalize()} cluster '{cluster_id}' has been idle for 14+ days "
                    f"(hit rate: {hit_rate:.1f}%, connections: {curr_connections_avg:.1f}). "
                    f"Delete unused cluster to save ~${monthly_cost:.2f}/month."
                ),
                "reason_codes": [
                    "elasticache",
                    "idle",
                    "low_hit_rate",
                    "low_connections",
                    "14_days",
                ],
                "evidence": evidence,
                "remediation": {
                    "steps": [
                        f"1. Verify cluster is truly idle: aws elasticache describe-cache-clusters --cache-cluster-id {cluster_id}",
                        f"2. Create final backup if needed: aws elasticache create-snapshot --cache-cluster-id {cluster_id} --snapshot-name {cluster_id}-final",
                        f"3. Delete cluster: aws elasticache delete-cache-cluster --cache-cluster-id {cluster_id}",
                    ],
                    "verification": f"aws elasticache describe-cache-clusters --cache-cluster-id {cluster_id} 2>&1 | grep CacheClusterNotFound",
                    "documentation": "https://docs.aws.amazon.com/AmazonElastiCache/latest/red-ug/Clusters.Delete.html",
                },
            }
        )

    return findings


def _estimate_elasticache_cost(
    node_type: str,
    num_nodes: int,
    region: str,
    pricing: Any,
) -> float:
    """Estimate monthly cost of an ElastiCache cluster.

    Cost = (hourly instance rate * 730 hours * num_nodes)

    Fallback to standard pricing if regional pricing unavailable.
    """
    # Common ElastiCache instance hourly rates (us-east-1, approximate)
    # Format: node_type -> hourly_rate_usd
    fallback_pricing = {
        "cache.t3.micro": 0.017,
        "cache.t3.small": 0.034,
        "cache.t3.medium": 0.068,
        "cache.t4g.micro": 0.016,
        "cache.t4g.small": 0.032,
        "cache.t4g.medium": 0.064,
        "cache.m5.large": 0.154,
        "cache.m5.xlarge": 0.308,
        "cache.m6g.large": 0.139,
        "cache.r5.large": 0.201,
        "cache.r5.xlarge": 0.402,
        "cache.r6g.large": 0.181,
    }

    # Try to get actual pricing from pricing module
    try:
        # This would require implementing ElastiCache pricing lookup
        # For now, use fallback
        hourly_rate = fallback_pricing.get(
            node_type, 0.10
        )  # Default to $0.10/hr if unknown
    except Exception:
        hourly_rate = fallback_pricing.get(node_type, 0.10)

    hours_per_month = 730
    monthly_cost = hourly_rate * hours_per_month * num_nodes

    return round(monthly_cost, 2)


def _extract_region_from_arn(arn: str) -> Optional[str]:
    """Extract AWS region from ARN.

    Example: arn:aws:elasticache:us-east-1:123456789012:cluster:my-cluster
    Returns: us-east-1
    """
    if not arn:
        return None
    parts = arn.split(":")
    if len(parts) >= 4:
        return parts[3] if parts[3] else None
    return None
