"""Security drift detection rules.

30+ rules covering open ports, public access, missing encryption,
IAM changes, logging, and network exposure.
"""

from __future__ import annotations

import re
from dataclasses import dataclass, field
from typing import Any, Callable, Optional

from ..store import DriftSeverity


@dataclass
class PolicyViolation:
    """A policy violation detected on a resource."""
    rule_id: str
    rule_name: str
    severity: DriftSeverity
    resource_address: str
    resource_type: str
    attribute: str
    actual_value: Any
    expected_condition: str
    message: str

    def to_dict(self) -> dict[str, Any]:
        return {
            "rule_id": self.rule_id,
            "rule_name": self.rule_name,
            "severity": self.severity.value,
            "resource_address": self.resource_address,
            "resource_type": self.resource_type,
            "attribute": self.attribute,
            "actual_value": self.actual_value,
            "expected_condition": self.expected_condition,
            "message": self.message,
        }


@dataclass
class SecurityRule:
    """A security drift detection rule."""
    id: str
    name: str
    description: str
    severity: DriftSeverity
    resource_types: list[str]  # Terraform resource types this applies to
    check: Callable[[str, dict[str, Any]], Optional[PolicyViolation]]
    enabled: bool = True


def _check_public_storage(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for publicly accessible storage."""
    for key in ("public_access", "allow_blob_public_access", "public_network_access", "publicly_accessible"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("true", "enabled", "blob", "container"):
            return PolicyViolation(
                rule_id="SEC-001", rule_name="Public Storage Access",
                severity=DriftSeverity.CRITICAL,
                resource_address=address, resource_type="storage",
                attribute=key, actual_value=val,
                expected_condition="Public access should be disabled",
                message=f"Storage has public access enabled ({key}={val})",
            )
    return None


def _check_encryption_disabled(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for missing encryption."""
    for key in attrs:
        if "encrypt" in key.lower():
            val = attrs[key]
            if str(val).lower() in ("false", "disabled", "none", ""):
                return PolicyViolation(
                    rule_id="SEC-002", rule_name="Encryption Disabled",
                    severity=DriftSeverity.CRITICAL,
                    resource_address=address, resource_type="*",
                    attribute=key, actual_value=val,
                    expected_condition="Encryption should be enabled",
                    message=f"Encryption is disabled ({key}={val})",
                )
    return None


def _check_open_cidr(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for 0.0.0.0/0 in security group/firewall rules."""
    for key in attrs:
        val = attrs[key]
        val_str = str(val)
        if "0.0.0.0/0" in val_str or "::/0" in val_str:
            if any(kw in key.lower() for kw in ("ingress", "cidr", "source_range", "ip_range", "allowed")):
                return PolicyViolation(
                    rule_id="SEC-003", rule_name="Open CIDR Block",
                    severity=DriftSeverity.CRITICAL,
                    resource_address=address, resource_type="security_group",
                    attribute=key, actual_value=val,
                    expected_condition="CIDR blocks should be restricted",
                    message=f"Wide-open CIDR (0.0.0.0/0) detected in {key}",
                )
    return None


def _check_ssh_open(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for SSH (port 22) open to the internet."""
    for key in attrs:
        val = attrs[key]
        if isinstance(val, list):
            for item in val:
                if isinstance(item, dict):
                    from_port = item.get("from_port", item.get("fromPort"))
                    cidrs = item.get("cidr_blocks", item.get("source_ranges", []))
                    if str(from_port) == "22" and any("0.0.0.0/0" in str(c) for c in (cidrs if isinstance(cidrs, list) else [cidrs])):
                        return PolicyViolation(
                            rule_id="SEC-004", rule_name="SSH Open to Internet",
                            severity=DriftSeverity.CRITICAL,
                            resource_address=address, resource_type="security_group",
                            attribute=key, actual_value=item,
                            expected_condition="SSH should be restricted to known IPs",
                            message="SSH (port 22) is open to 0.0.0.0/0",
                        )
    return None


def _check_rdp_open(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for RDP (port 3389) open to the internet."""
    for key in attrs:
        val = attrs[key]
        if isinstance(val, list):
            for item in val:
                if isinstance(item, dict):
                    from_port = item.get("from_port", item.get("fromPort"))
                    cidrs = item.get("cidr_blocks", item.get("source_ranges", []))
                    if str(from_port) == "3389" and any("0.0.0.0/0" in str(c) for c in (cidrs if isinstance(cidrs, list) else [cidrs])):
                        return PolicyViolation(
                            rule_id="SEC-005", rule_name="RDP Open to Internet",
                            severity=DriftSeverity.CRITICAL,
                            resource_address=address, resource_type="security_group",
                            attribute=key, actual_value=item,
                            expected_condition="RDP should be restricted to known IPs",
                            message="RDP (port 3389) is open to 0.0.0.0/0",
                        )
    return None


def _check_logging_disabled(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for disabled logging/monitoring."""
    for key in attrs:
        if any(kw in key.lower() for kw in ("logging", "monitoring", "audit", "diagnostic")):
            val = attrs[key]
            if str(val).lower() in ("false", "disabled", "none", "off"):
                return PolicyViolation(
                    rule_id="SEC-006", rule_name="Logging Disabled",
                    severity=DriftSeverity.HIGH,
                    resource_address=address, resource_type="*",
                    attribute=key, actual_value=val,
                    expected_condition="Logging/monitoring should be enabled",
                    message=f"Logging/monitoring disabled ({key}={val})",
                )
    return None


def _check_https_only(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check that HTTPS is enforced."""
    for key in ("https_only", "enable_https_traffic_only", "minimum_tls_version", "ssl_enforcement"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("false", "disabled"):
            return PolicyViolation(
                rule_id="SEC-007", rule_name="HTTPS Not Enforced",
                severity=DriftSeverity.HIGH,
                resource_address=address, resource_type="*",
                attribute=key, actual_value=val,
                expected_condition="HTTPS should be enforced",
                message=f"HTTPS/TLS not enforced ({key}={val})",
            )
    return None


def _check_min_tls(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check minimum TLS version."""
    for key in ("minimum_tls_version", "min_tls_version", "tls_version"):
        val = attrs.get(key)
        if val is not None:
            val_str = str(val).replace("_", ".").replace("TLS", "").replace("tls", "").strip()
            if val_str in ("1.0", "1.1", "TLS1_0", "TLS1_1"):
                return PolicyViolation(
                    rule_id="SEC-008", rule_name="Weak TLS Version",
                    severity=DriftSeverity.HIGH,
                    resource_address=address, resource_type="*",
                    attribute=key, actual_value=val,
                    expected_condition="TLS >= 1.2",
                    message=f"Weak TLS version ({val}). Minimum should be TLS 1.2.",
                )
    return None


def _check_deletion_protection(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check deletion protection on critical resources."""
    for key in ("deletion_protection", "termination_protection", "prevent_destroy"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("false", "disabled"):
            return PolicyViolation(
                rule_id="SEC-009", rule_name="Deletion Protection Off",
                severity=DriftSeverity.MEDIUM,
                resource_address=address, resource_type="*",
                attribute=key, actual_value=val,
                expected_condition="Deletion protection should be enabled",
                message=f"Deletion protection disabled ({key}={val})",
            )
    return None


def _check_public_ip(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for unexpected public IP assignments."""
    for key in ("public_ip", "public_ip_address", "associate_public_ip_address"):
        val = attrs.get(key)
        if val is not None and str(val).lower() not in ("", "none", "null", "false"):
            return PolicyViolation(
                rule_id="SEC-010", rule_name="Public IP Assigned",
                severity=DriftSeverity.MEDIUM,
                resource_address=address, resource_type="compute",
                attribute=key, actual_value=val,
                expected_condition="Resources should not have public IPs unless explicitly needed",
                message=f"Public IP assigned ({key}={val})",
            )
    return None


def _check_iam_wildcard(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for IAM wildcard permissions."""
    for key in attrs:
        if "policy" in key.lower() or "statement" in key.lower():
            val_str = str(attrs[key])
            if '"Action": "*"' in val_str or '"action": "*"' in val_str or '"Effect": "Allow"' in val_str and '"Resource": "*"' in val_str:
                return PolicyViolation(
                    rule_id="SEC-011", rule_name="IAM Wildcard Permission",
                    severity=DriftSeverity.CRITICAL,
                    resource_address=address, resource_type="iam",
                    attribute=key, actual_value="<policy>",
                    expected_condition="IAM policies should follow least-privilege principle",
                    message="IAM policy contains wildcard (*) permissions",
                )
    return None


def _check_backup_disabled(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for disabled backups."""
    for key in ("backup_retention_period", "backup_retention", "backup_enabled", "automated_backup"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("0", "false", "disabled", "none"):
            return PolicyViolation(
                rule_id="SEC-012", rule_name="Backups Disabled",
                severity=DriftSeverity.HIGH,
                resource_address=address, resource_type="database",
                attribute=key, actual_value=val,
                expected_condition="Automated backups should be enabled",
                message=f"Backups disabled ({key}={val})",
            )
    return None


def _check_versioning_disabled(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for disabled versioning on storage."""
    for key in ("versioning_enabled", "versioning", "enable_versioning"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("false", "disabled", "suspended"):
            return PolicyViolation(
                rule_id="SEC-013", rule_name="Storage Versioning Off",
                severity=DriftSeverity.MEDIUM,
                resource_address=address, resource_type="storage",
                attribute=key, actual_value=val,
                expected_condition="Storage versioning should be enabled",
                message=f"Storage versioning disabled ({key}={val})",
            )
    return None


def _check_default_vpc(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for use of default VPC/network."""
    for key in ("vpc_id", "network"):
        val = attrs.get(key)
        if val and "default" in str(val).lower():
            return PolicyViolation(
                rule_id="SEC-014", rule_name="Default VPC/Network",
                severity=DriftSeverity.MEDIUM,
                resource_address=address, resource_type="network",
                attribute=key, actual_value=val,
                expected_condition="Resources should use custom VPCs, not default",
                message=f"Resource using default VPC/network ({key}={val})",
            )
    return None


def _check_network_policy_disabled(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for disabled network policies on Kubernetes."""
    for key in ("network_policy", "network_policy_config"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("false", "disabled"):
            return PolicyViolation(
                rule_id="SEC-015", rule_name="Network Policy Disabled",
                severity=DriftSeverity.HIGH,
                resource_address=address, resource_type="kubernetes",
                attribute=key, actual_value=val,
                expected_condition="Network policies should be enabled",
                message="Kubernetes network policies are disabled",
            )
    return None


def _check_database_public(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for publicly accessible databases."""
    for key in ("publicly_accessible", "public_network_access", "public_access_enabled"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("true", "enabled"):
            return PolicyViolation(
                rule_id="SEC-016", rule_name="Database Publicly Accessible",
                severity=DriftSeverity.CRITICAL,
                resource_address=address, resource_type="database",
                attribute=key, actual_value=val,
                expected_condition="Databases should not be publicly accessible",
                message=f"Database is publicly accessible ({key}={val})",
            )
    return None


def _check_key_rotation(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for disabled key rotation."""
    for key in ("enable_key_rotation", "key_rotation_enabled", "rotation_period"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("false", "disabled", "0"):
            return PolicyViolation(
                rule_id="SEC-017", rule_name="Key Rotation Disabled",
                severity=DriftSeverity.HIGH,
                resource_address=address, resource_type="kms",
                attribute=key, actual_value=val,
                expected_condition="Key rotation should be enabled",
                message=f"Key rotation is disabled ({key}={val})",
            )
    return None


def _check_all_ports_open(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for security groups allowing all ports."""
    for key in attrs:
        val = attrs[key]
        if isinstance(val, list):
            for item in val:
                if isinstance(item, dict):
                    from_port = item.get("from_port", item.get("fromPort", -1))
                    to_port = item.get("to_port", item.get("toPort", -1))
                    protocol = str(item.get("protocol", item.get("ip_protocol", "")))
                    if (str(from_port) == "0" and str(to_port) == "65535") or protocol == "-1":
                        return PolicyViolation(
                            rule_id="SEC-018", rule_name="All Ports Open",
                            severity=DriftSeverity.CRITICAL,
                            resource_address=address, resource_type="security_group",
                            attribute=key, actual_value=item,
                            expected_condition="Security groups should restrict port ranges",
                            message="Security group allows all ports (0-65535)",
                        )
    return None


def _check_imdsv1(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for EC2 instances using IMDSv1."""
    http_tokens = attrs.get("metadata_options.http_tokens", attrs.get("http_tokens"))
    if http_tokens and str(http_tokens).lower() == "optional":
        return PolicyViolation(
            rule_id="SEC-019", rule_name="IMDSv1 Enabled",
            severity=DriftSeverity.HIGH,
            resource_address=address, resource_type="aws_instance",
            attribute="metadata_options.http_tokens", actual_value=http_tokens,
            expected_condition="IMDSv2 should be required (http_tokens=required)",
            message="EC2 instance allows IMDSv1 (http_tokens=optional). Use IMDSv2.",
        )
    return None


def _check_waf_missing(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for load balancers without WAF."""
    waf = attrs.get("web_application_firewall", attrs.get("waf_configuration"))
    if waf is not None and str(waf).lower() in ("false", "disabled", "none", "{}"):
        return PolicyViolation(
            rule_id="SEC-020", rule_name="WAF Not Configured",
            severity=DriftSeverity.MEDIUM,
            resource_address=address, resource_type="load_balancer",
            attribute="waf", actual_value=waf,
            expected_condition="WAF should be configured on public-facing load balancers",
            message="WAF not configured on load balancer",
        )
    return None


def _check_flow_logs(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for VPC/VNet without flow logs."""
    for key in ("flow_log", "enable_flow_log", "flow_logs_enabled"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("false", "disabled", "none"):
            return PolicyViolation(
                rule_id="SEC-021", rule_name="Flow Logs Disabled",
                severity=DriftSeverity.MEDIUM,
                resource_address=address, resource_type="network",
                attribute=key, actual_value=val,
                expected_condition="VPC/VNet flow logs should be enabled",
                message=f"Network flow logs disabled ({key}={val})",
            )
    return None


def _check_secure_boot(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for disabled secure boot."""
    for key in ("enable_secure_boot", "secure_boot", "shielded_instance_config.enable_secure_boot"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("false", "disabled"):
            return PolicyViolation(
                rule_id="SEC-022", rule_name="Secure Boot Disabled",
                severity=DriftSeverity.MEDIUM,
                resource_address=address, resource_type="compute",
                attribute=key, actual_value=val,
                expected_condition="Secure boot should be enabled",
                message=f"Secure boot disabled ({key}={val})",
            )
    return None


def _check_disk_encryption(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for unencrypted disks."""
    for key in ("disk_encryption", "encrypted", "kms_key_self_link", "disk_encryption_key"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("false", "none", ""):
            return PolicyViolation(
                rule_id="SEC-023", rule_name="Disk Not Encrypted",
                severity=DriftSeverity.HIGH,
                resource_address=address, resource_type="compute",
                attribute=key, actual_value=val,
                expected_condition="Disks should be encrypted",
                message=f"Disk encryption not configured ({key}={val})",
            )
    return None


def _check_multi_az(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for single-AZ database deployments."""
    for key in ("multi_az", "availability_type"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("false", "zonal", "single"):
            return PolicyViolation(
                rule_id="SEC-024", rule_name="Single AZ Deployment",
                severity=DriftSeverity.MEDIUM,
                resource_address=address, resource_type="database",
                attribute=key, actual_value=val,
                expected_condition="Databases should be multi-AZ for resilience",
                message=f"Database is single-AZ ({key}={val})",
            )
    return None


def _check_public_access_block(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check S3 public access block configuration."""
    for key in ("block_public_acls", "block_public_policy", "ignore_public_acls", "restrict_public_buckets"):
        val = attrs.get(key, attrs.get(f"public_access_block.{key}"))
        if val is not None and str(val).lower() in ("false",):
            return PolicyViolation(
                rule_id="SEC-025", rule_name="S3 Public Access Block Missing",
                severity=DriftSeverity.CRITICAL,
                resource_address=address, resource_type="aws_s3_bucket",
                attribute=key, actual_value=val,
                expected_condition="All S3 public access block settings should be true",
                message=f"S3 public access block not fully enabled ({key}={val})",
            )
    return None


def _check_access_keys_rotation(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for old/unrotated access keys."""
    # This would typically check the age of the key
    status = attrs.get("status", attrs.get("access_key_status"))
    if status and str(status).lower() == "active":
        # Flag as informational — actual age check requires more context
        return PolicyViolation(
            rule_id="SEC-026", rule_name="Active Access Key",
            severity=DriftSeverity.INFO,
            resource_address=address, resource_type="iam",
            attribute="status", actual_value=status,
            expected_condition="Access keys should be rotated regularly",
            message="Active access key detected — verify rotation policy",
        )
    return None


def _check_root_account(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for root account usage indicators."""
    user = attrs.get("user", attrs.get("username", ""))
    if str(user).lower() in ("root", "admin", "administrator"):
        return PolicyViolation(
            rule_id="SEC-027", rule_name="Root/Admin Account Used",
            severity=DriftSeverity.HIGH,
            resource_address=address, resource_type="iam",
            attribute="user", actual_value=user,
            expected_condition="Root/admin accounts should not be used directly",
            message=f"Resource associated with root/admin account ({user})",
        )
    return None


def _check_secrets_in_env(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for potential secrets in environment variables."""
    for key in attrs:
        if "environment" in key.lower() or "env_var" in key.lower():
            val_str = str(attrs[key]).lower()
            secret_patterns = ["password", "secret", "api_key", "apikey", "token", "private_key"]
            for pattern in secret_patterns:
                if pattern in val_str and "arn:" not in val_str and "vault" not in val_str:
                    return PolicyViolation(
                        rule_id="SEC-028", rule_name="Secrets in Environment",
                        severity=DriftSeverity.HIGH,
                        resource_address=address, resource_type="*",
                        attribute=key, actual_value="<redacted>",
                        expected_condition="Secrets should use secret managers, not env vars",
                        message=f"Potential secret in environment variable ({key})",
                    )
    return None


def _check_unrestricted_egress(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for unrestricted egress rules."""
    for key in attrs:
        if "egress" in key.lower():
            val = attrs[key]
            val_str = str(val)
            if "0.0.0.0/0" in val_str and ("-1" in val_str or "all" in val_str.lower()):
                return PolicyViolation(
                    rule_id="SEC-029", rule_name="Unrestricted Egress",
                    severity=DriftSeverity.LOW,
                    resource_address=address, resource_type="security_group",
                    attribute=key, actual_value=val,
                    expected_condition="Egress should be restricted where possible",
                    message="Unrestricted egress to 0.0.0.0/0 on all protocols",
                )
    return None


def _check_container_privileged(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for privileged containers."""
    for key in ("privileged", "security_context.privileged"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("true",):
            return PolicyViolation(
                rule_id="SEC-030", rule_name="Privileged Container",
                severity=DriftSeverity.CRITICAL,
                resource_address=address, resource_type="container",
                attribute=key, actual_value=val,
                expected_condition="Containers should not run in privileged mode",
                message="Container running in privileged mode",
            )
    return None


def _check_service_account_key(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for GCP service account keys (prefer workload identity)."""
    if "service_account_key" in attrs.get("type", "").lower():
        return PolicyViolation(
            rule_id="SEC-031", rule_name="Service Account Key Exists",
            severity=DriftSeverity.MEDIUM,
            resource_address=address, resource_type="iam",
            attribute="type", actual_value="service_account_key",
            expected_condition="Use workload identity instead of service account keys",
            message="Service account key exists — prefer workload identity federation",
        )
    return None


def _check_cors_wildcard(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for wildcard CORS configuration."""
    for key in attrs:
        if "cors" in key.lower():
            val_str = str(attrs[key])
            if '"*"' in val_str or "'*'" in val_str:
                return PolicyViolation(
                    rule_id="SEC-032", rule_name="CORS Wildcard Origin",
                    severity=DriftSeverity.MEDIUM,
                    resource_address=address, resource_type="*",
                    attribute=key, actual_value="*",
                    expected_condition="CORS should restrict allowed origins",
                    message="CORS configured with wildcard (*) origin",
                )
    return None


# All security rules
SECURITY_RULES: list[SecurityRule] = [
    SecurityRule("SEC-001", "Public Storage Access", "Storage with public access enabled", DriftSeverity.CRITICAL,
                 ["azurerm_storage_account", "aws_s3_bucket", "google_storage_bucket"], _check_public_storage),
    SecurityRule("SEC-002", "Encryption Disabled", "Resources without encryption", DriftSeverity.CRITICAL,
                 ["*"], _check_encryption_disabled),
    SecurityRule("SEC-003", "Open CIDR Block", "0.0.0.0/0 in network rules", DriftSeverity.CRITICAL,
                 ["aws_security_group", "azurerm_network_security_group", "google_compute_firewall"], _check_open_cidr),
    SecurityRule("SEC-004", "SSH Open to Internet", "SSH port 22 open to 0.0.0.0/0", DriftSeverity.CRITICAL,
                 ["aws_security_group", "azurerm_network_security_group", "google_compute_firewall"], _check_ssh_open),
    SecurityRule("SEC-005", "RDP Open to Internet", "RDP port 3389 open to 0.0.0.0/0", DriftSeverity.CRITICAL,
                 ["aws_security_group", "azurerm_network_security_group", "google_compute_firewall"], _check_rdp_open),
    SecurityRule("SEC-006", "Logging Disabled", "Logging/monitoring turned off", DriftSeverity.HIGH,
                 ["*"], _check_logging_disabled),
    SecurityRule("SEC-007", "HTTPS Not Enforced", "HTTP traffic allowed", DriftSeverity.HIGH,
                 ["azurerm_storage_account", "azurerm_app_service", "aws_lb", "aws_cloudfront_distribution"], _check_https_only),
    SecurityRule("SEC-008", "Weak TLS Version", "TLS version below 1.2", DriftSeverity.HIGH,
                 ["*"], _check_min_tls),
    SecurityRule("SEC-009", "Deletion Protection Off", "Missing deletion/termination protection", DriftSeverity.MEDIUM,
                 ["aws_db_instance", "google_compute_instance", "azurerm_mssql_server"], _check_deletion_protection),
    SecurityRule("SEC-010", "Public IP Assigned", "Unexpected public IP on resource", DriftSeverity.MEDIUM,
                 ["aws_instance", "google_compute_instance", "azurerm_virtual_machine"], _check_public_ip),
    SecurityRule("SEC-011", "IAM Wildcard Permission", "IAM policy with wildcard actions/resources", DriftSeverity.CRITICAL,
                 ["aws_iam_role", "aws_iam_policy", "google_project_iam_binding"], _check_iam_wildcard),
    SecurityRule("SEC-012", "Backups Disabled", "Automated backups turned off", DriftSeverity.HIGH,
                 ["aws_db_instance", "azurerm_mssql_server", "google_sql_database_instance"], _check_backup_disabled),
    SecurityRule("SEC-013", "Storage Versioning Off", "Storage versioning disabled", DriftSeverity.MEDIUM,
                 ["aws_s3_bucket", "google_storage_bucket"], _check_versioning_disabled),
    SecurityRule("SEC-014", "Default VPC/Network", "Using default VPC instead of custom", DriftSeverity.MEDIUM,
                 ["aws_instance", "aws_security_group"], _check_default_vpc),
    SecurityRule("SEC-015", "Network Policy Disabled", "Kubernetes network policies off", DriftSeverity.HIGH,
                 ["google_container_cluster", "aws_eks_cluster"], _check_network_policy_disabled),
    SecurityRule("SEC-016", "Database Publicly Accessible", "Database exposed to internet", DriftSeverity.CRITICAL,
                 ["aws_db_instance", "azurerm_mssql_server", "google_sql_database_instance"], _check_database_public),
    SecurityRule("SEC-017", "Key Rotation Disabled", "KMS key rotation off", DriftSeverity.HIGH,
                 ["aws_kms_key", "google_kms_crypto_key", "azurerm_key_vault_key"], _check_key_rotation),
    SecurityRule("SEC-018", "All Ports Open", "Security group allows all ports", DriftSeverity.CRITICAL,
                 ["aws_security_group", "azurerm_network_security_group", "google_compute_firewall"], _check_all_ports_open),
    SecurityRule("SEC-019", "IMDSv1 Enabled", "EC2 instance metadata v1 allowed", DriftSeverity.HIGH,
                 ["aws_instance"], _check_imdsv1),
    SecurityRule("SEC-020", "WAF Not Configured", "Missing WAF on public load balancer", DriftSeverity.MEDIUM,
                 ["aws_lb", "azurerm_application_gateway", "google_compute_backend_service"], _check_waf_missing),
    SecurityRule("SEC-021", "Flow Logs Disabled", "VPC/VNet flow logs off", DriftSeverity.MEDIUM,
                 ["aws_vpc", "azurerm_virtual_network", "google_compute_network"], _check_flow_logs),
    SecurityRule("SEC-022", "Secure Boot Disabled", "Secure boot turned off", DriftSeverity.MEDIUM,
                 ["google_compute_instance", "azurerm_virtual_machine"], _check_secure_boot),
    SecurityRule("SEC-023", "Disk Not Encrypted", "Unencrypted disk volumes", DriftSeverity.HIGH,
                 ["aws_ebs_volume", "google_compute_disk", "azurerm_managed_disk"], _check_disk_encryption),
    SecurityRule("SEC-024", "Single AZ Deployment", "Database in single availability zone", DriftSeverity.MEDIUM,
                 ["aws_db_instance", "google_sql_database_instance"], _check_multi_az),
    SecurityRule("SEC-025", "S3 Public Access Block Missing", "S3 bucket missing public access block", DriftSeverity.CRITICAL,
                 ["aws_s3_bucket", "aws_s3_bucket_public_access_block"], _check_public_access_block),
    SecurityRule("SEC-026", "Active Access Key", "Unrotated access key", DriftSeverity.INFO,
                 ["aws_iam_access_key"], _check_access_keys_rotation),
    SecurityRule("SEC-027", "Root/Admin Account Used", "Resource tied to root account", DriftSeverity.HIGH,
                 ["aws_iam_user", "aws_iam_role"], _check_root_account),
    SecurityRule("SEC-028", "Secrets in Environment", "Potential secrets in env vars", DriftSeverity.HIGH,
                 ["aws_lambda_function", "aws_ecs_task_definition", "google_cloud_run_service"], _check_secrets_in_env),
    SecurityRule("SEC-029", "Unrestricted Egress", "All egress traffic allowed", DriftSeverity.LOW,
                 ["aws_security_group", "azurerm_network_security_group"], _check_unrestricted_egress),
    SecurityRule("SEC-030", "Privileged Container", "Container in privileged mode", DriftSeverity.CRITICAL,
                 ["aws_ecs_task_definition", "google_cloud_run_service", "azurerm_container_group"], _check_container_privileged),
    SecurityRule("SEC-031", "Service Account Key", "GCP service account key exists", DriftSeverity.MEDIUM,
                 ["google_service_account_key"], _check_service_account_key),
    SecurityRule("SEC-032", "CORS Wildcard Origin", "CORS allows all origins", DriftSeverity.MEDIUM,
                 ["*"], _check_cors_wildcard),
]


class SecurityPolicyEngine:
    """Evaluate security policies against resource attributes.

    Runs all 32 security rules against the provided resource to detect
    security-relevant drift or misconfigurations.
    """

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

    def evaluate(
        self,
        resource_address: str,
        resource_type: str,
        attributes: dict[str, Any],
    ) -> list[PolicyViolation]:
        """Evaluate all enabled rules against a resource.

        Args:
            resource_address: Resource address (e.g., aws_instance.web).
            resource_type: Terraform-style resource type.
            attributes: Flat attribute map.

        Returns:
            List of policy violations found.
        """
        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("Rule %s failed on %s: %s", rule.id, resource_address, e)
        return violations

    def evaluate_batch(
        self,
        resources: dict[str, tuple[str, dict[str, Any]]],
    ) -> dict[str, list[PolicyViolation]]:
        """Evaluate policies on multiple resources.

        Args:
            resources: Map of address -> (resource_type, attributes).

        Returns:
            Map of address -> violations.
        """
        results: dict[str, list[PolicyViolation]] = {}
        for address, (res_type, attrs) in resources.items():
            violations = self.evaluate(address, res_type, attrs)
            if violations:
                results[address] = violations
        return results

    def get_enabled_rules(self) -> list[dict[str, Any]]:
        """Get all enabled rules."""
        return [
            {
                "id": r.id,
                "name": r.name,
                "description": r.description,
                "severity": r.severity.value,
                "resource_types": r.resource_types,
                "enabled": r.enabled,
            }
            for r in self.rules
            if r.enabled
        ]

    def disable_rule(self, rule_id: str) -> bool:
        """Disable a rule by ID."""
        for rule in self.rules:
            if rule.id == rule_id:
                rule.enabled = False
                return True
        return False

    def enable_rule(self, rule_id: str) -> bool:
        """Enable a rule by ID."""
        for rule in self.rules:
            if rule.id == rule_id:
                rule.enabled = True
                return True
        return False


# Make logger available
import logging
logger = logging.getLogger(__name__)
