"""
EBS (Elastic Block Storage) Detectors

Detects cost optimization opportunities for EBS volumes and snapshots.
"""

from datetime import datetime, timedelta
from typing import Any, Dict, List

from stacksage.pricing import ebs_gb_month, snapshot_gb_month


def _region_from_volume(v: Dict[str, Any]) -> str:
    region = v.get("Region")
    if region:
        return region
    az = v.get("AvailabilityZone")
    if isinstance(az, str) and len(az) >= 2:
        # e.g. us-east-1a -> us-east-1
        return az[:-1]
    return "unknown"


def _tag_kv_lower(tags: Any) -> Dict[str, str]:
    out: Dict[str, str] = {}
    if not isinstance(tags, list):
        return out
    for t in tags:
        if not isinstance(t, dict):
            continue
        k = t.get("Key")
        if not k:
            continue
        v = t.get("Value")
        out[str(k).lower()] = "" if v is None else str(v).lower()
    return out


def _parse_utc_datetime(value: Any) -> datetime | None:
    """Best-effort parse for AWS-style timestamps.

    Returns a timezone-aware datetime (UTC) or None if parsing fails.
    """
    try:
        from datetime import timezone

        if value is None:
            return None
        if isinstance(value, datetime):
            dt = value
        elif isinstance(value, str):
            s = value.strip()
            # Common AWS formats
            try:
                dt = datetime.fromisoformat(s.replace("Z", "+00:00"))
            except Exception:
                dt = None
                for fmt in (
                    "%Y-%m-%dT%H:%M:%S%z",
                    "%Y-%m-%d %H:%M:%S%z",
                    "%Y-%m-%dT%H:%M:%S",
                    "%Y-%m-%d %H:%M:%S",
                ):
                    try:
                        dt = datetime.strptime(s, fmt)
                        break
                    except Exception:
                        continue
                if dt is None:
                    return None
        else:
            return None

        if dt.tzinfo is None:
            dt = dt.replace(tzinfo=timezone.utc)
        return dt.astimezone(timezone.utc)
    except Exception:
        return None


def detect_unattached_ebs(volumes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """
    Detect EBS volumes with no attachments that are likely abandoned.

    Only flags volumes unattached for >7 days to avoid false positives during:
    - Troubleshooting (temporary detachment)
    - Maintenance windows
    - Instance replacements

    Production threshold: 7 days to avoid false positives
    """
    findings = []
    from datetime import timezone

    now = datetime.now(timezone.utc)

    # Production threshold
    MIN_DAYS_UNATTACHED = 7

    for v in volumes:
        if not v.get("Attachments"):
            # Check create time to estimate how long it's been unattached
            # If volume was recently created, it may be in provisioning/setup
            create_time = _parse_utc_datetime(v.get("CreateTime"))
            if create_time is None:
                # If we can't reliably determine age, skip to reduce false positives.
                continue

            days_since_create = (now - create_time).days
            # Only flag if older than threshold (likely abandoned, not just temporary)
            if days_since_create < MIN_DAYS_UNATTACHED:
                continue

            size = v.get("Size", 0)
            volume_type = v.get("VolumeType", "gp3")
            region = _region_from_volume(v)
            est = ebs_gb_month(size, volume_type, region)

            # Dynamic severity based on cost
            if est >= 50:
                severity = "high"
            elif est >= 20:
                severity = "medium"
            else:
                severity = "low"

            findings.append(
                {
                    "type": "unattached_ebs",
                    "resource_type": "ebs",
                    "id": v.get("VolumeId"),
                    "region": region,
                    "size_gb": size,
                    "estimated_monthly_cost_usd": est,
                    "estimated_monthly_savings_usd": est,
                    "potential_savings": est,
                    "confidence": 0.95,
                    "severity": severity,
                    "recommended_action": "snapshot-and-delete",
                    "explanation": f"EBS volume ({size} GB) unattached for >{MIN_DAYS_UNATTACHED} days. Create snapshot for safety, then delete if unused.",
                    "evidence": {
                        "inventory": {
                            "state": v.get("State"),
                            "volume_type": volume_type,
                            "size_gb": size,
                            "attachments": v.get("Attachments"),
                            "create_time": v.get("CreateTime"),
                        },
                        "assessment": {
                            "days_unattached": days_since_create,
                            "min_days_unattached": MIN_DAYS_UNATTACHED,
                        },
                    },
                    "verification_commands": [
                        f"aws ec2 describe-volumes --volume-ids {v.get('VolumeId')} --region {region}",
                    ],
                    "remediation_commands": [
                        "# Create snapshot before deleting:",
                        f"aws ec2 create-snapshot --volume-id {v.get('VolumeId')} --description 'Backup before deletion'",
                        "# Then delete volume:",
                        f"aws ec2 delete-volume --volume-id {v.get('VolumeId')}",
                    ],
                }
            )
    return findings


def detect_gp2_to_gp3_migration(volumes: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
    """
    Recommend gp2 → gp3 migration with conditions.

    gp3 is generally better than gp2:
    - 20% cheaper ($0.08/GB vs $0.10/GB)
    - Better baseline: 3000 IOPS, 125 MB/s (vs gp2's scaled IOPS)
    - More predictable performance (no burst credits)

    However, we only recommend migration when:
    1. Volume size ≥ 50 GB (savings ≥ $1/month makes it worthwhile)
    2. Volume is in-use or recently used (not a forgotten test volume)

    Safe to migrate in 99% of cases. gp3 baseline (3000 IOPS) equals gp2's
    burst performance, so workloads relying on bursting still work fine.
    """
    findings = []
    for v in volumes:
        vol_type = v.get("VolumeType", "")
        if vol_type == "gp2":
            size = v.get("Size", 0)
            state = v.get("State", "unknown")

            # Only recommend for in-use/attached volumes. Unattached volumes are handled
            # by the unattached EBS detector, and recommending gp3 there is noise.
            has_attachments = bool(v.get("Attachments"))
            if state != "in-use" and not has_attachments:
                continue

            # Only recommend if volume ≥ 50 GB (minimum $1/month savings)
            if size < 50:
                continue

            # Calculate savings using pricing functions with regional pricing
            region = _region_from_volume(v)
            gp2_cost = ebs_gb_month(size, "gp2", region)
            gp3_cost = ebs_gb_month(size, "gp3", region)
            savings = round(gp2_cost - gp3_cost, 2)

            # Build explanation based on volume size
            if size >= 1000:  # 1TB+
                explanation = f"Migrate large gp2 volume ({size} GB) to gp3 for ${savings}/mo savings (20% reduction). gp3 provides better baseline performance (3000 IOPS guaranteed vs gp2's {min(16000, size * 3)} IOPS with burst credits). Migration is online with no downtime."
                confidence = 0.95
            elif (
                size >= 334
            ):  # gp2 baseline is 3*size IOPS, so 334GB = 1002 IOPS (below gp3's 3000)
                explanation = f"Migrate gp2 volume ({size} GB) to gp3 for ${savings}/mo savings + better performance. gp3 baseline (3000 IOPS) exceeds your gp2 baseline ({size * 3} IOPS). No downtime required."
                confidence = 0.98
            else:  # Small volumes that rely on burst credits
                explanation = f"Migrate gp2 volume ({size} GB) to gp3 for ${savings}/mo savings (20% cost reduction). gp3 provides consistent 3000 IOPS baseline (vs gp2's {size * 3} IOPS baseline + burst credits up to 3000). Most workloads benefit from predictable performance. Test in non-prod first if workload is burst-dependent."
                confidence = 0.90

            findings.append(
                {
                    "type": "gp2_to_gp3_migration",
                    "resource_type": "ebs",
                    "id": v.get("VolumeId"),
                    "region": region,
                    "size_gb": size,
                    "volume_type": vol_type,
                    "state": state,
                    "estimated_monthly_cost_usd": gp2_cost,
                    "estimated_monthly_savings_usd": savings,
                    "potential_savings": savings,
                    "confidence": confidence,
                    "severity": "medium" if savings >= 10 else "low",
                    "recommended_action": "migrate-to-gp3",
                    "explanation": explanation,
                    "evidence": {
                        "inventory": {
                            "state": state,
                            "volume_type": vol_type,
                            "size_gb": size,
                            "attachments": v.get("Attachments"),
                        },
                        "estimation": {
                            "gp2_monthly_cost_usd": gp2_cost,
                            "gp3_monthly_cost_usd": gp3_cost,
                        },
                    },
                    "verification_commands": [
                        f"aws ec2 describe-volumes --volume-ids {v.get('VolumeId')} --region {region}",
                        f"aws ec2 describe-volume-attribute --volume-id {v.get('VolumeId')} --attribute volumeType --region {region}",
                    ],
                    "remediation_commands": [
                        "# Test in non-prod first, then migrate:",
                        f"aws ec2 modify-volume --volume-id {v.get('VolumeId')} --volume-type gp3",
                        "# Monitor performance after migration:",
                        f"aws cloudwatch get-metric-statistics --namespace AWS/EBS --metric-name VolumeReadOps --dimensions Name=VolumeId,Value={v.get('VolumeId')} --start-time $(date -u -v-1d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period 300 --statistics Average",
                    ],
                }
            )
    return findings


def detect_old_snapshots(
    snapshots: List[Dict[str, Any]], days_threshold: int = 180
) -> List[Dict[str, Any]]:
    """
    Flag snapshots older than threshold with smart filtering.

    Excludes:
    - AMI-related snapshots (used by Amazon Machine Images)
    - Snapshots with retention/backup tags
    - Snapshots with 'Production' or 'Critical' in description

    Changed threshold from 90 to 180 days to reduce false positives
    for legitimate quarterly/annual backups.
    """
    findings = []
    try:
        from datetime import timezone

        cutoff = datetime.now(timezone.utc) - timedelta(days=days_threshold)
    except Exception:
        cutoff = None

    for s in snapshots:
        snapshot_id = s.get("SnapshotId")
        description = s.get("Description", "").lower()
        tags = s.get("Tags", [])

        # Skip AMI-related snapshots (critical for AMI functionality)
        if "created by createimage" in description or "ami" in description:
            continue

        # Check for retention/backup tags that indicate intentional long-term storage
        tag_dict = _tag_kv_lower(tags)
        retention_keywords = [
            "backup",
            "retain",
            "keep",
            "archive",
            "compliance",
            "annual",
            "yearly",
            "monthly",
        ]
        tag_keys = list(tag_dict.keys())
        tag_vals = list(tag_dict.values())
        if any(any(keyword in k for k in tag_keys) for keyword in retention_keywords):
            continue
        if any(any(keyword in v for v in tag_vals) for keyword in retention_keywords):
            continue

        # Skip snapshots with "production", "prod", "critical" tags or description
        if any(
            keyword in description for keyword in ["production", "prod", "critical"]
        ):
            continue
        if any(
            keyword in tag_dict.get("environment", "")
            for keyword in ["prod", "production"]
        ):
            continue

        st = _parse_utc_datetime(s.get("StartTime"))

        if cutoff and st and st < cutoff:
            size_gb = s.get("VolumeSize", 0)
            region = s.get("Region")
            est = snapshot_gb_month(size_gb, region)
            days_old = (datetime.now(timezone.utc) - st).days

            # Dynamic severity based on age and cost
            if days_old > 730 and est > 20:  # >2 years and expensive
                severity = "medium"
                confidence = 0.90
            elif days_old > 365:  # >1 year
                severity = "low"
                confidence = 0.85
            else:
                severity = "low"
                confidence = 0.75

            findings.append(
                {
                    "type": "old_snapshot",
                    "resource_type": "snapshot",
                    "id": snapshot_id,
                    "start_time": (st.isoformat() if st else None),
                    "days_old": days_old,
                    "size_gb": size_gb,
                    "estimated_monthly_cost_usd": est,
                    "estimated_monthly_savings_usd": est,
                    "potential_savings": est,
                    "confidence": confidence,
                    "severity": severity,
                    "recommended_action": "review-and-delete",
                    "explanation": f"Snapshot is {days_old} days old ({size_gb} GB). No retention tags found. Consider deleting if no longer needed for compliance or recovery.",
                    "evidence": {
                        "inventory": {
                            "volume_id": s.get("VolumeId"),
                            "start_time": (st.isoformat() if st else None),
                            "size_gb": size_gb,
                            "description": s.get("Description"),
                            "tags": tags,
                        },
                        "assessment": {
                            "days_old": days_old,
                            "days_threshold": days_threshold,
                        },
                    },
                    "verification_commands": [
                        f"aws ec2 describe-snapshots --snapshot-ids {snapshot_id} --region {region}",
                    ],
                    "remediation_commands": [
                        "# Verify snapshot is not needed:",
                        f"aws ec2 describe-snapshots --snapshot-ids {snapshot_id}",
                        "# Delete if confirmed unused:",
                        f"aws ec2 delete-snapshot --snapshot-id {snapshot_id}",
                    ],
                }
            )
    return findings


def detect_snapshot_consolidation(
    snapshots: List[Dict[str, Any]],
) -> List[Dict[str, Any]]:
    """Detect volumes with many snapshots; suggest consolidation."""
    from collections import defaultdict

    findings = []
    by_vol = defaultdict(list)
    for s in snapshots:
        vol_id = s.get("VolumeId")
        if vol_id:
            by_vol[vol_id].append(s)
    for vol_id, snaps in by_vol.items():
        if len(snaps) >= 5:
            total_size = sum(s.get("VolumeSize", 0) for s in snaps)
            # Use first snapshot's region for pricing (all should be same region)
            region = snaps[0].get("Region") if snaps else None
            est = snapshot_gb_month(total_size, region)
            findings.append(
                {
                    "type": "snapshot_consolidation",
                    "resource_type": "snapshot",
                    "id": vol_id,
                    "snapshot_count": len(snaps),
                    "total_size_gb": total_size,
                    "estimated_monthly_cost_usd": est,
                    "estimated_monthly_savings_usd": round(est * 0.5, 2),
                    "potential_savings": round(est * 0.5, 2),
                    "confidence": 0.7,
                    "severity": "low",
                    "recommended_action": "consolidate-snapshots",
                    "explanation": f"Volume has {len(snaps)} snapshots. Consider keeping only recent snapshots to reduce storage cost.",
                    "evidence": {
                        "inventory": {
                            "volume_id": vol_id,
                            "snapshot_ids": [s.get("SnapshotId") for s in snaps],
                            "snapshot_count": len(snaps),
                        },
                        "estimation": {
                            "total_size_gb": total_size,
                        },
                    },
                    "verification_commands": [
                        f"aws ec2 describe-snapshots --filters Name=volume-id,Values={vol_id} --region {region}",
                    ],
                    "remediation_commands": [
                        f"# Review snapshots for volume {vol_id}, then manually delete older ones"
                    ],
                }
            )
    return findings


def detect_ebs_overprovisioned_performance(
    session,
    volumes: List[Dict[str, Any]],
    days: int = 14,
    budget=None,
    cw_avg_func=None,
    iops_util_threshold: float = 0.2,
    throughput_util_threshold: float = 0.2,
) -> List[Dict[str, Any]]:
    """
    Detect EBS volumes with provisioned performance far above observed usage.

    Uses CloudWatch metrics to compare average IOPS/throughput against provisioned
    values for gp3/io1/io2 volumes.
    """
    findings = []
    if session is None:
        return findings
    if not volumes:
        return findings

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

    period = 300
    for v in volumes:
        vol_id = v.get("VolumeId")
        vol_type = (v.get("VolumeType") or "").lower()
        region = _region_from_volume(v)
        if not vol_id or vol_type not in ("gp3", "io1", "io2"):
            continue

        provisioned_iops = int(v.get("Iops") or 0)
        provisioned_throughput = int(v.get("Throughput") or 0)

        # gp3 only charges for IOPS/throughput above baselines
        gp3_base_iops = 3000
        gp3_base_throughput = 125
        if vol_type == "gp3":
            extra_iops = max(0, provisioned_iops - gp3_base_iops)
            extra_throughput = max(0, provisioned_throughput - gp3_base_throughput)
            if extra_iops == 0 and extra_throughput == 0:
                continue

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

        read_ops = cw_avg_func(
            session,
            "AWS/EBS",
            "VolumeReadOps",
            [{"Name": "VolumeId", "Value": vol_id}],
            period=period,
            days=days,
            statistic="Sum",
            region_name=region,
            budget=budget,
            errors=cw_errors,
            evidence=cw_detail,
        )
        write_ops = cw_avg_func(
            session,
            "AWS/EBS",
            "VolumeWriteOps",
            [{"Name": "VolumeId", "Value": vol_id}],
            period=period,
            days=days,
            statistic="Sum",
            region_name=region,
            budget=budget,
            errors=cw_errors,
            evidence=cw_detail,
        )
        read_bytes = cw_avg_func(
            session,
            "AWS/EBS",
            "VolumeReadBytes",
            [{"Name": "VolumeId", "Value": vol_id}],
            period=period,
            days=days,
            statistic="Sum",
            region_name=region,
            budget=budget,
            errors=cw_errors,
            evidence=cw_detail,
        )
        write_bytes = cw_avg_func(
            session,
            "AWS/EBS",
            "VolumeWriteBytes",
            [{"Name": "VolumeId", "Value": vol_id}],
            period=period,
            days=days,
            statistic="Sum",
            region_name=region,
            budget=budget,
            errors=cw_errors,
            evidence=cw_detail,
        )

        if None in (read_ops, write_ops, read_bytes, write_bytes):
            continue

        avg_iops = (float(read_ops) + float(write_ops)) / period
        avg_mbps = (float(read_bytes) + float(write_bytes)) / period / (1024**2)

        # Determine provisioned targets
        if vol_type == "gp3":
            provisioned_iops = max(provisioned_iops, gp3_base_iops)
            provisioned_throughput = max(provisioned_throughput, gp3_base_throughput)

        iops_over = (
            provisioned_iops > 0 and avg_iops < provisioned_iops * iops_util_threshold
        )
        throughput_over = (
            provisioned_throughput > 0
            and avg_mbps < provisioned_throughput * throughput_util_threshold
        )

        if not (iops_over or throughput_over):
            continue

        recommended_iops = None
        recommended_throughput = None
        if provisioned_iops > 0:
            baseline = gp3_base_iops if vol_type == "gp3" else 100
            recommended_iops = max(baseline, int(avg_iops * 2))
        if provisioned_throughput > 0:
            baseline = gp3_base_throughput if vol_type == "gp3" else 0
            recommended_throughput = max(baseline, int(avg_mbps * 2))

        severity = "medium" if provisioned_iops >= 10000 else "low"
        findings.append(
            {
                "type": "ebs_overprovisioned_performance",
                "resource_type": "ebs",
                "id": vol_id,
                "region": region,
                "volume_type": vol_type,
                "size_gb": v.get("Size"),
                "provisioned_iops": provisioned_iops,
                "provisioned_throughput_mbps": provisioned_throughput,
                "avg_iops": round(avg_iops, 2),
                "avg_throughput_mbps": round(avg_mbps, 2),
                "estimated_monthly_cost_usd": None,
                "estimated_monthly_savings_usd": None,
                "potential_savings": None,
                "confidence": 0.75,
                "severity": severity,
                "recommended_action": "reduce-provisioned-performance",
                "explanation": (
                    f"Provisioned performance appears high for observed usage over {days} days. "
                    f"Avg IOPS {avg_iops:.1f} (prov {provisioned_iops}), "
                    f"avg throughput {avg_mbps:.1f} MB/s (prov {provisioned_throughput} MB/s)."
                ),
                "evidence": {
                    "inventory": {
                        "volume_type": vol_type,
                        "size_gb": v.get("Size"),
                        "iops": provisioned_iops,
                        "throughput_mbps": provisioned_throughput,
                        "state": v.get("State"),
                    },
                    "utilization": {
                        "avg_iops": round(avg_iops, 2),
                        "avg_throughput_mbps": round(avg_mbps, 2),
                        "iops_threshold_pct": iops_util_threshold,
                        "throughput_threshold_pct": throughput_util_threshold,
                        "lookback_days": days,
                    },
                    "recommendation": {
                        "recommended_iops": recommended_iops,
                        "recommended_throughput_mbps": recommended_throughput,
                    },
                },
                "verification_commands": [
                    f"aws ec2 describe-volumes --volume-ids {vol_id} --region {region}",
                    f"aws cloudwatch get-metric-statistics --namespace AWS/EBS --metric-name VolumeReadOps --dimensions Name=VolumeId,Value={vol_id} --start-time $(date -u -v-{days}d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period {period} --statistics Sum --region {region}",
                    f"aws cloudwatch get-metric-statistics --namespace AWS/EBS --metric-name VolumeWriteOps --dimensions Name=VolumeId,Value={vol_id} --start-time $(date -u -v-{days}d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period {period} --statistics Sum --region {region}",
                    f"aws cloudwatch get-metric-statistics --namespace AWS/EBS --metric-name VolumeReadBytes --dimensions Name=VolumeId,Value={vol_id} --start-time $(date -u -v-{days}d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period {period} --statistics Sum --region {region}",
                    f"aws cloudwatch get-metric-statistics --namespace AWS/EBS --metric-name VolumeWriteBytes --dimensions Name=VolumeId,Value={vol_id} --start-time $(date -u -v-{days}d '+%Y-%m-%dT%H:%M:%S') --end-time $(date -u '+%Y-%m-%dT%H:%M:%S') --period {period} --statistics Sum --region {region}",
                ],
                "remediation_commands": (
                    [
                        "# Test in non-production first:",
                        (
                            f"aws ec2 modify-volume --volume-id {vol_id} --iops {recommended_iops}"
                            if recommended_iops
                            else None
                        ),
                        (
                            f"aws ec2 modify-volume --volume-id {vol_id} --throughput {recommended_throughput}"
                            if recommended_throughput
                            else None
                        ),
                    ]
                    if (recommended_iops or recommended_throughput)
                    else []
                ),
                "metrics_error": ";".join(cw_errors) if cw_errors else None,
                "cw_metrics": cw_detail.get("metrics"),
            }
        )

    return findings
