"""Cost drift detection rules.

Detect oversized instances, unused resources, missing tags,
and cost-related configuration changes.
"""

from __future__ import annotations

import logging
from dataclasses import dataclass
from typing import Any, Callable, Optional

from ..store import DriftSeverity
from .security import PolicyViolation, SecurityRule

logger = logging.getLogger(__name__)


def _check_oversized_instance(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for oversized instance types."""
    expensive_types = {
        # AWS
        "x1", "x2", "p3", "p4", "p5", "inf1", "inf2", "trn1",
        "r6g.16xlarge", "r6g.12xlarge", "m6g.16xlarge",
        # Azure
        "standard_e64", "standard_m128", "standard_nc", "standard_nd",
        # GCP
        "n2-highmem-96", "n2-highmem-128", "a2-highgpu", "a2-megagpu",
    }
    for key in ("instance_type", "machine_type", "sku.name", "vm_size"):
        val = attrs.get(key, "")
        val_lower = str(val).lower()
        for exp_type in expensive_types:
            if exp_type in val_lower:
                return PolicyViolation(
                    rule_id="COST-001", rule_name="Oversized Instance",
                    severity=DriftSeverity.HIGH,
                    resource_address=address, resource_type="compute",
                    attribute=key, actual_value=val,
                    expected_condition="Instance size should match workload requirements",
                    message=f"Potentially oversized instance type: {val}",
                )
    return None


def _check_missing_tags(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for missing cost-allocation tags."""
    required_tags = {"environment", "env", "project", "team", "owner", "cost-center", "costcenter"}
    found_tags: set[str] = set()
    for key in attrs:
        if key.startswith("tags.") or key.startswith("labels."):
            tag_name = key.split(".", 1)[1].lower()
            found_tags.add(tag_name)
    # Check if at least 2 of the required tags exist
    matches = found_tags & required_tags
    if len(matches) < 2:
        return PolicyViolation(
            rule_id="COST-002", rule_name="Missing Cost Tags",
            severity=DriftSeverity.MEDIUM,
            resource_address=address, resource_type="*",
            attribute="tags", actual_value=list(found_tags),
            expected_condition=f"At least 2 cost-allocation tags required: {sorted(required_tags)}",
            message=f"Only {len(matches)} cost-allocation tag(s) found: {sorted(matches)}",
        )
    return None


def _check_stopped_instance(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for stopped instances (still incur EBS/disk costs)."""
    state = attrs.get("state", attrs.get("status", ""))
    if str(state).lower() in ("stopped", "terminated", "deallocated"):
        return PolicyViolation(
            rule_id="COST-003", rule_name="Stopped Instance",
            severity=DriftSeverity.MEDIUM,
            resource_address=address, resource_type="compute",
            attribute="state", actual_value=state,
            expected_condition="Stopped instances still incur storage costs",
            message=f"Instance is {state} — still incurring storage costs. Consider terminating.",
        )
    return None


def _check_unattached_volume(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for unattached storage volumes."""
    state = attrs.get("state", attrs.get("status", ""))
    attachment = attrs.get("attachment", attrs.get("attachments", ""))
    if str(state).lower() in ("available", "unattached") or (not attachment and "volume" in attrs.get("type", "").lower()):
        return PolicyViolation(
            rule_id="COST-004", rule_name="Unattached Volume",
            severity=DriftSeverity.MEDIUM,
            resource_address=address, resource_type="storage",
            attribute="state", actual_value=state,
            expected_condition="Storage volumes should be attached or deleted",
            message=f"Unattached storage volume ({state}) — wasting money",
        )
    return None


def _check_old_snapshot(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for old snapshots that may no longer be needed."""
    # This is a placeholder — real implementation would check age
    return None


def _check_provisioned_iops(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for unnecessarily provisioned IOPS."""
    iops = attrs.get("iops", attrs.get("provisioned_iops"))
    if iops and int(str(iops)) > 10000:
        return PolicyViolation(
            rule_id="COST-005", rule_name="High Provisioned IOPS",
            severity=DriftSeverity.MEDIUM,
            resource_address=address, resource_type="storage",
            attribute="iops", actual_value=iops,
            expected_condition="Verify high IOPS are justified by workload",
            message=f"High provisioned IOPS ({iops}) — verify this is needed",
        )
    return None


def _check_reserved_capacity(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for on-demand resources that could use reserved pricing."""
    pricing = attrs.get("tenancy", attrs.get("pricing_model", attrs.get("purchase_option", "")))
    instance_type = attrs.get("instance_type", attrs.get("machine_type", ""))
    if instance_type and str(pricing).lower() in ("on-demand", "on_demand", "default", ""):
        return PolicyViolation(
            rule_id="COST-006", rule_name="On-Demand Pricing",
            severity=DriftSeverity.LOW,
            resource_address=address, resource_type="compute",
            attribute="pricing", actual_value=pricing or "on-demand",
            expected_condition="Consider reserved instances or savings plans for stable workloads",
            message=f"Resource using on-demand pricing — consider reserved capacity",
        )
    return None


def _check_nat_gateway(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for NAT gateways (expensive) that may not be needed."""
    # NAT gateways cost ~$32/month minimum
    res_type = attrs.get("type", "").lower()
    if "nat" in res_type:
        return PolicyViolation(
            rule_id="COST-007", rule_name="NAT Gateway Present",
            severity=DriftSeverity.INFO,
            resource_address=address, resource_type="network",
            attribute="type", actual_value=res_type,
            expected_condition="NAT gateways are expensive — verify necessity",
            message="NAT gateway detected (~$32/month minimum). Verify it's needed.",
        )
    return None


def _check_storage_tier(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for premium storage that could use standard tier."""
    for key in ("storage_class", "account_tier", "sku.tier", "disk_type"):
        val = attrs.get(key, "")
        val_lower = str(val).lower()
        if val_lower in ("premium", "premium_lrs", "pd-ssd", "io1", "io2"):
            return PolicyViolation(
                rule_id="COST-008", rule_name="Premium Storage Tier",
                severity=DriftSeverity.LOW,
                resource_address=address, resource_type="storage",
                attribute=key, actual_value=val,
                expected_condition="Verify premium storage is justified",
                message=f"Premium storage tier ({val}) — verify it's needed for performance",
            )
    return None


# All cost rules
COST_RULES: list[SecurityRule] = [
    SecurityRule("COST-001", "Oversized Instance", "Expensive instance type", DriftSeverity.HIGH,
                 ["aws_instance", "google_compute_instance", "azurerm_virtual_machine"], _check_oversized_instance),
    SecurityRule("COST-002", "Missing Cost Tags", "Missing cost-allocation tags", DriftSeverity.MEDIUM,
                 ["*"], _check_missing_tags),
    SecurityRule("COST-003", "Stopped Instance", "Instance stopped but still billing", DriftSeverity.MEDIUM,
                 ["aws_instance", "google_compute_instance", "azurerm_virtual_machine"], _check_stopped_instance),
    SecurityRule("COST-004", "Unattached Volume", "Storage volume not attached", DriftSeverity.MEDIUM,
                 ["aws_ebs_volume", "google_compute_disk", "azurerm_managed_disk"], _check_unattached_volume),
    SecurityRule("COST-005", "High Provisioned IOPS", "Expensive provisioned IOPS", DriftSeverity.MEDIUM,
                 ["aws_ebs_volume", "aws_db_instance"], _check_provisioned_iops),
    SecurityRule("COST-006", "On-Demand Pricing", "Could use reserved pricing", DriftSeverity.LOW,
                 ["aws_instance", "google_compute_instance"], _check_reserved_capacity),
    SecurityRule("COST-007", "NAT Gateway Present", "Expensive NAT gateway", DriftSeverity.INFO,
                 ["aws_nat_gateway", "azurerm_nat_gateway", "google_compute_router_nat"], _check_nat_gateway),
    SecurityRule("COST-008", "Premium Storage Tier", "Premium storage may be unnecessary", DriftSeverity.LOW,
                 ["aws_ebs_volume", "azurerm_storage_account", "google_compute_disk"], _check_storage_tier),
]


class CostPolicyEngine:
    """Evaluate cost-related policies against resource attributes."""

    def __init__(self, rules: Optional[list[SecurityRule]] = None) -> None:
        self.rules = rules or COST_RULES

    def evaluate(
        self,
        resource_address: str,
        resource_type: str,
        attributes: dict[str, Any],
    ) -> list[PolicyViolation]:
        """Evaluate all enabled cost rules against a resource."""
        violations: list[PolicyViolation] = []
        for rule in self.rules:
            if not rule.enabled:
                continue
            if "*" not in rule.resource_types and resource_type not in rule.resource_types:
                continue
            try:
                violation = rule.check(resource_address, attributes)
                if violation:
                    violations.append(violation)
            except Exception as e:
                logger.warning("Cost rule %s failed on %s: %s", rule.id, resource_address, e)
        return violations
