"""
RDS (Relational Database Service) cost optimization detectors.

This module contains detectors for:
- Underutilized RDS instances (CPU + Storage analysis)
- RDS rightsizing recommendations
"""

from typing import Any, Dict, List

from stacksage.pricing import (
    PRICING_RDS_HOURLY,
    ec2_hourly_to_monthly,
    estimate_rds_monthly_cost,
)


def detect_underutilized_rds(
    session,
    rds_list: List[Dict[str, Any]],
    cpu_threshold: float = 10.0,
    days: int = 14,
    budget=None,
    cw_avg_func=None,
) -> List[Dict[str, Any]]:
    """
    RDS rightsizing detector with CPU + Storage analysis.

    Checks:
    1. CPU utilization (<10% indicates over-provisioned compute)
    2. FreeStorageSpace (>70% free indicates over-provisioned storage)
    3. Provides specific instance type downsize recommendations

    Args:
        session: Boto3 session for CloudWatch access
        rds_list: List of RDS instance dictionaries
        cpu_threshold: Maximum CPU % for underutilization (default: 10.0)
        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
    if not rds_list:
        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

    # RDS instance type sizing map (common downsizing paths)
    downsize_map = {
        "db.t3.2xlarge": "db.t3.xlarge",
        "db.t3.xlarge": "db.t3.large",
        "db.t3.large": "db.t3.medium",
        "db.t3.medium": "db.t3.small",
        "db.m5.2xlarge": "db.m5.xlarge",
        "db.m5.xlarge": "db.m5.large",
        "db.m5.large": "db.m5.xlarge",
        "db.r5.2xlarge": "db.r5.xlarge",
        "db.r5.xlarge": "db.r5.large",
        "db.r5.large": "db.r5.xlarge",
    }

    daily_period = 86400

    def _cw_error_classification(
        cw_errors: List[str], namespace: str, metric: str
    ) -> str | None:
        prefix = f"cw_error:{namespace}:{metric}:"
        for e in reversed(cw_errors or []):
            if isinstance(e, str) and e.startswith(prefix):
                return e.split(":")[-1]
        return None

    def _metric_status_from_errors(
        cw_errors: List[str], namespace: str, metric: str
    ) -> str:
        cls = _cw_error_classification(cw_errors, namespace, metric)
        if cls in ("no_data", "not_authorized", "throttle"):
            return cls
        if cls is None:
            return "no_data"
        return "error"

    for db in rds_list:
        dbid = db.get("DBInstanceIdentifier")
        region = db.get("Region")
        instance_class = db.get("DBInstanceClass", "")
        allocated_storage = db.get("AllocatedStorage", 0)
        engine = db.get("Engine", "")
        multi_az = bool(db.get("MultiAZ"))
        has_replicas = bool(db.get("ReadReplicaDBInstanceIdentifiers")) or bool(
            db.get("ReadReplicaSourceDBInstanceIdentifier")
        )

        if not dbid:
            continue

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

        # Query CPU utilization
        cpu = cw_avg_func(
            session,
            "AWS/RDS",
            "CPUUtilization",
            [{"Name": "DBInstanceIdentifier", "Value": dbid}],
            days=days,
            region_name=region,
            budget=budget,
            errors=cw_errors,
            evidence=cw_detail,
        )

        # Query FreeStorageSpace (use Minimum to be conservative about tight storage)
        # Aurora (including Serverless v2) uses shared auto-scaling storage and never
        # publishes FreeStorageSpace — skip the metric to avoid wasting CW budget.
        is_aurora = engine.lower().startswith("aurora")
        free_storage_bytes = None
        if not is_aurora:
            free_storage_bytes = cw_avg_func(
                session,
                "AWS/RDS",
                "FreeStorageSpace",
                [{"Name": "DBInstanceIdentifier", "Value": dbid}],
                period=daily_period,
                days=days,
                statistic="Minimum",
                region_name=region,
                budget=budget,
                errors=cw_errors,
                evidence=cw_detail,
            )

        if cpu is None and budget and budget.remaining <= 0:
            findings.append(
                {
                    "type": "underutilized_rds",
                    "resource_type": "rds",
                    "id": dbid,
                    "engine": engine,
                    "metric_status": "skipped_budget",
                    "confidence": 0.4,
                    "severity": "low",
                    "recommended_action": "review-and-rightsize",
                    "explanation": "Metric query skipped due to budget exhaustion",
                    "evidence": {
                        "inventory": {
                            "engine": engine,
                            "instance_class": instance_class,
                            "allocated_storage_gb": allocated_storage,
                            "multi_az": multi_az,
                            "has_replicas": has_replicas,
                        },
                        "utilization": {
                            "status": "skipped_budget",
                            "lookback_days": days,
                        },
                    },
                    "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} --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": [],
                    "metrics_error": ";".join(cw_errors) if cw_errors else None,
                    "cw_metrics": cw_detail.get("metrics"),
                }
            )
            continue

        if cpu is None:
            metric_status = _metric_status_from_errors(
                cw_errors, "AWS/RDS", "CPUUtilization"
            )
            findings.append(
                {
                    "type": "underutilized_rds",
                    "resource_type": "rds",
                    "id": dbid,
                    "engine": engine,
                    "metric_status": metric_status,
                    "confidence": 0.3,
                    "severity": "low",
                    "recommended_action": "review-and-rightsize",
                    "explanation": "Could not determine RDS utilization from CloudWatch metrics (no data or insufficient permissions).",
                    "evidence": {
                        "inventory": {
                            "engine": engine,
                            "instance_class": instance_class,
                            "allocated_storage_gb": allocated_storage,
                            "multi_az": multi_az,
                            "has_replicas": has_replicas,
                        },
                        "utilization": {
                            "status": metric_status,
                            "lookback_days": days,
                        },
                    },
                    "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} --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": [],
                    "metrics_error": ";".join(cw_errors) if cw_errors else None,
                    "cw_metrics": cw_detail.get("metrics"),
                }
            )
            continue

        # Calculate storage utilization
        storage_utilization = None
        if free_storage_bytes is not None and allocated_storage > 0:
            free_storage_gb = free_storage_bytes / (1024**3)
            storage_utilization = (1 - (free_storage_gb / allocated_storage)) * 100

        storage_tight = bool(
            storage_utilization is not None and float(storage_utilization) >= 80.0
        )

        # Check for underutilization
        is_cpu_underutilized = cpu < cpu_threshold
        is_storage_overprovisioned = (
            storage_utilization is not None and storage_utilization < 30
        )  # <30% used = >70% free

        if is_cpu_underutilized or is_storage_overprovisioned:
            # Determine downsizing recommendation
            recommended_instance = downsize_map.get(instance_class, None)
            recommended_storage = None
            if is_storage_overprovisioned and allocated_storage > 100:
                # Recommend 50% reduction if heavily over-provisioned
                recommended_storage = max(100, int(allocated_storage * 0.5))

            # Guardrails:
            # - If storage is tight (high utilization), do not recommend downsizing.
            # - If Multi-AZ or replicas are present, soften the recommendation/confidence.
            if storage_tight:
                recommended_instance = None
                recommended_storage = None

            # Calculate actual savings using pricing functions
            est_savings = 0
            current_cost = 0

            if recommended_instance:
                # Calculate actual cost difference between current and recommended instance
                current_hourly = PRICING_RDS_HOURLY.get(instance_class, 0.10)
                recommended_hourly = PRICING_RDS_HOURLY.get(recommended_instance, 0.08)
                instance_savings = ec2_hourly_to_monthly(
                    current_hourly - recommended_hourly
                )
                est_savings += instance_savings
                current_cost = estimate_rds_monthly_cost(
                    instance_class, allocated_storage, "gp2", False, region
                )
            else:
                current_cost = estimate_rds_monthly_cost(
                    instance_class, allocated_storage, "gp2", False, region
                )

            if recommended_storage:
                # Storage savings calculation
                savings_gb = allocated_storage - recommended_storage
                storage_savings = savings_gb * 0.115  # $0.115/GB-month for gp2
                est_savings += storage_savings

            explanation_parts = []
            if is_cpu_underutilized:
                explanation_parts.append(f"CPU {cpu:.1f}% avg over {days} days")
            if is_storage_overprovisioned:
                explanation_parts.append(
                    f"Storage {storage_utilization:.1f}% utilized ({allocated_storage} GB allocated)"
                )

            if storage_tight and storage_utilization is not None:
                explanation_parts.append(
                    f"Storage is tight ({storage_utilization:.1f}% utilized); skipping downsizing recommendation"
                )
            if multi_az:
                explanation_parts.append(
                    "Multi-AZ enabled (treat rightsizing with caution)"
                )
            if has_replicas:
                explanation_parts.append(
                    "Read replica topology detected (treat rightsizing with caution)"
                )

            explanation = ". ".join(explanation_parts) + ". "

            if recommended_instance and recommended_storage:
                explanation += f"Recommend downsizing to {recommended_instance} and reducing storage to {recommended_storage} GB for ${est_savings:.2f}/mo savings."
            elif recommended_instance:
                explanation += f"Recommend downsizing to {recommended_instance} for ${est_savings:.2f}/mo savings."
            elif recommended_storage:
                explanation += f"Recommend reducing storage from {allocated_storage} GB to {recommended_storage} GB for ${est_savings:.2f}/mo savings."
            else:
                explanation += (
                    "Review utilization and configuration before making changes."
                )

            # Dynamic severity based on savings potential
            if est_savings >= 100:
                severity = "high"
            elif est_savings >= 50:
                severity = "medium"
            else:
                severity = "low"

            findings.append(
                {
                    "type": "underutilized_rds",
                    "resource_type": "rds",
                    "id": dbid,
                    "engine": engine,
                    "instance_class": instance_class,
                    "cpu_avg": round(cpu, 2),
                    "storage_utilization_pct": (
                        round(storage_utilization, 2) if storage_utilization else None
                    ),
                    "allocated_storage_gb": allocated_storage,
                    "recommended_instance_class": recommended_instance,
                    "recommended_storage_gb": recommended_storage,
                    "estimated_monthly_cost_usd": round(current_cost, 2),
                    "estimated_monthly_savings_usd": round(est_savings, 2),
                    "potential_savings": round(est_savings, 2),
                    "confidence": (
                        0.75
                        if (recommended_instance or recommended_storage)
                        and (multi_az or has_replicas)
                        else (0.80 if recommended_instance else 0.70)
                    ),
                    "severity": severity,
                    "recommended_action": (
                        "rightsize-instance-and-storage"
                        if (recommended_instance or recommended_storage)
                        else "review-and-rightsize"
                    ),
                    "explanation": explanation,
                    "evidence": {
                        "inventory": {
                            "engine": engine,
                            "instance_class": instance_class,
                            "allocated_storage_gb": allocated_storage,
                            "multi_az": multi_az,
                            "has_replicas": has_replicas,
                        },
                        "utilization": {
                            "cpu_avg": round(cpu, 2),
                            "cpu_threshold": cpu_threshold,
                            "storage_utilization_pct": (
                                round(storage_utilization, 2)
                                if storage_utilization is not None
                                else None
                            ),
                            "lookback_days": days,
                        },
                        "assessment": {
                            "storage_tight": storage_tight,
                            "recommended_instance_class": recommended_instance,
                            "recommended_storage_gb": recommended_storage,
                        },
                    },
                    "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} --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}",
                        f"aws cloudwatch get-metric-statistics --namespace AWS/RDS --metric-name FreeStorageSpace --dimensions Name=DBInstanceIdentifier,Value={dbid} --start-time $(date -u -v-{days}d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 86400 --statistics Minimum --region {region}",
                    ],
                    "remediation_commands": (
                        [
                            cmd
                            for cmd in [
                                "# Test in non-production first!",
                                "# Downsize instance (requires brief downtime):",
                                (
                                    f"aws rds modify-db-instance --db-instance-identifier {dbid} --db-instance-class {recommended_instance or instance_class} --apply-immediately"
                                    if recommended_instance
                                    else None
                                ),
                                "# Reduce storage (cannot reduce below current usage):",
                                (
                                    f"aws rds modify-db-instance --db-instance-identifier {dbid} --allocated-storage {recommended_storage} --apply-immediately"
                                    if recommended_storage
                                    else None
                                ),
                            ]
                            if cmd is not None
                        ]
                        if recommended_instance or recommended_storage
                        else []
                    ),
                    "metrics_error": ";".join(cw_errors) if cw_errors else None,
                    "cw_metrics": cw_detail.get("metrics"),
                }
            )
    return findings


def detect_rds_low_connections(
    session,
    rds_list: List[Dict[str, Any]],
    days: int = 14,
    budget=None,
    cw_avg_func=None,
    connections_threshold: float = 1.0,
) -> List[Dict[str, Any]]:
    """
    Guardrail: detect RDS instances with persistently low connections.

    Uses DatabaseConnections metric to flag instances that appear idle.
    """
    findings = []
    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")
        region = db.get("Region")
        instance_class = db.get("DBInstanceClass", "")
        allocated_storage = db.get("AllocatedStorage", 0)
        engine = db.get("Engine", "")
        multi_az = bool(db.get("MultiAZ"))
        has_replicas = bool(db.get("ReadReplicaDBInstanceIdentifiers")) or bool(
            db.get("ReadReplicaSourceDBInstanceIdentifier")
        )

        if not dbid:
            continue

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

        avg_connections = cw_avg_func(
            session,
            "AWS/RDS",
            "DatabaseConnections",
            [{"Name": "DBInstanceIdentifier", "Value": dbid}],
            days=days,
            region_name=region,
            budget=budget,
            errors=cw_errors,
            evidence=cw_detail,
        )

        if avg_connections is None:
            continue

        if avg_connections <= connections_threshold:
            current_cost = estimate_rds_monthly_cost(
                instance_class,
                allocated_storage,
                "gp2",
                multi_az,
                region,
            )

            confidence = 0.75 if not (multi_az or has_replicas) else 0.6
            if current_cost >= 100:
                severity = "high"
            elif current_cost >= 50:
                severity = "medium"
            else:
                severity = "low"

            explanation_parts = [
                f"Average connections {avg_connections:.2f} over {days} days"
            ]
            if multi_az:
                explanation_parts.append("Multi-AZ enabled (treat cautiously)")
            if has_replicas:
                explanation_parts.append("Read replicas detected (treat cautiously)")

            findings.append(
                {
                    "type": "rds_low_connections",
                    "resource_type": "rds",
                    "id": dbid,
                    "engine": engine,
                    "instance_class": instance_class,
                    "region": region,
                    "estimated_monthly_cost_usd": round(current_cost, 2),
                    "estimated_monthly_savings_usd": round(current_cost, 2),
                    "potential_savings": round(current_cost, 2),
                    "confidence": confidence,
                    "severity": severity,
                    "recommended_action": "review-and-stop-if-unused",
                    "explanation": ". ".join(explanation_parts) + ".",
                    "evidence": {
                        "inventory": {
                            "engine": engine,
                            "instance_class": instance_class,
                            "allocated_storage_gb": allocated_storage,
                            "multi_az": multi_az,
                            "has_replicas": has_replicas,
                        },
                        "utilization": {
                            "avg_connections": round(avg_connections, 2),
                            "connections_threshold": connections_threshold,
                            "lookback_days": days,
                        },
                    },
                    "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 DatabaseConnections --dimensions Name=DBInstanceIdentifier,Value={dbid} --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": [],
                    "metrics_error": ";".join(cw_errors) if cw_errors else None,
                    "cw_metrics": cw_detail.get("metrics"),
                }
            )
    return findings
