"""Compliance drift detection rules.

Rules for SOC2, HIPAA, PCI-DSS, GDPR, and CIS benchmark compliance.
"""

from __future__ import annotations

import logging
from typing import Any, Optional

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

logger = logging.getLogger(__name__)


def _check_data_residency(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check data residency — resources in unexpected regions."""
    allowed_regions = {
        # Common compliant regions
        "us-east-1", "us-west-2", "eu-west-1", "eu-central-1",
        "uksouth", "ukwest", "northeurope", "westeurope",
        "europe-west1", "europe-west2", "us-central1",
    }
    for key in ("location", "region", "zone"):
        val = attrs.get(key, "")
        val_str = str(val).lower().strip()
        if val_str and val_str not in allowed_regions:
            # Check if it's a sub-region (e.g., us-east-1a)
            base_region = val_str.rstrip("abcdef")
            if base_region not in allowed_regions:
                return PolicyViolation(
                    rule_id="COMP-001", rule_name="Data Residency Violation",
                    severity=DriftSeverity.HIGH,
                    resource_address=address, resource_type="*",
                    attribute=key, actual_value=val,
                    expected_condition=f"Resource should be in approved regions: {sorted(allowed_regions)}",
                    message=f"Resource in non-approved region: {val}",
                )
    return None


def _check_encryption_at_rest(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check encryption at rest (required by most compliance frameworks)."""
    encryption_keys = [
        "encryption", "encrypt", "kms", "server_side_encryption",
        "encryption_at_rest", "storage_encrypted", "disk_encryption",
    ]
    has_encryption = False
    for key in attrs:
        for enc_key in encryption_keys:
            if enc_key in key.lower():
                val = attrs[key]
                if str(val).lower() not in ("false", "disabled", "none", ""):
                    has_encryption = True
                    break
        if has_encryption:
            break

    # Only flag if the resource type typically requires encryption
    encrypt_required = {
        "aws_db_instance", "aws_s3_bucket", "aws_ebs_volume", "aws_rds_cluster",
        "azurerm_storage_account", "azurerm_mssql_server", "azurerm_managed_disk",
        "google_sql_database_instance", "google_storage_bucket", "google_compute_disk",
    }
    res_type = attrs.get("_resource_type", "")
    if res_type in encrypt_required and not has_encryption:
        return PolicyViolation(
            rule_id="COMP-002", rule_name="Missing Encryption at Rest",
            severity=DriftSeverity.CRITICAL,
            resource_address=address, resource_type=res_type,
            attribute="encryption", actual_value="not configured",
            expected_condition="Encryption at rest required for compliance",
            message="No encryption at rest detected on a resource that requires it",
        )
    return None


def _check_audit_logging(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check that audit logging is enabled (SOC2/HIPAA requirement)."""
    logging_keys = ["logging", "audit_log", "diagnostic_setting", "cloud_watch_logs", "access_logging"]
    has_logging = False
    for key in attrs:
        for log_key in logging_keys:
            if log_key in key.lower():
                val = attrs[key]
                if str(val).lower() not in ("false", "disabled", "none"):
                    has_logging = True
                    break
        if has_logging:
            break
    if not has_logging:
        return PolicyViolation(
            rule_id="COMP-003", rule_name="Audit Logging Missing",
            severity=DriftSeverity.HIGH,
            resource_address=address, resource_type="*",
            attribute="logging", actual_value="not configured",
            expected_condition="Audit logging required for SOC2/HIPAA compliance",
            message="No audit logging detected on resource",
        )
    return None


def _check_mfa_delete(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check MFA delete on S3 buckets (PCI-DSS requirement)."""
    mfa = attrs.get("mfa_delete", attrs.get("versioning.mfa_delete"))
    if mfa is not None and str(mfa).lower() in ("false", "disabled"):
        return PolicyViolation(
            rule_id="COMP-004", rule_name="MFA Delete Not Enabled",
            severity=DriftSeverity.MEDIUM,
            resource_address=address, resource_type="aws_s3_bucket",
            attribute="mfa_delete", actual_value=mfa,
            expected_condition="MFA delete should be enabled for PCI-DSS compliance",
            message="S3 MFA delete not enabled",
        )
    return None


def _check_access_logging(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check for access logging on load balancers and storage."""
    for key in ("access_logs", "access_logging", "logging_enabled"):
        val = attrs.get(key)
        if val is not None and str(val).lower() in ("false", "disabled"):
            return PolicyViolation(
                rule_id="COMP-005", rule_name="Access Logging Disabled",
                severity=DriftSeverity.MEDIUM,
                resource_address=address, resource_type="*",
                attribute=key, actual_value=val,
                expected_condition="Access logging required for compliance audit trail",
                message=f"Access logging disabled ({key}={val})",
            )
    return None


def _check_retention_period(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check log retention periods meet minimum requirements."""
    for key in ("retention_in_days", "retention_period", "log_retention"):
        val = attrs.get(key)
        if val is not None:
            try:
                days = int(str(val))
                if 0 < days < 90:  # Most frameworks require 90+ days
                    return PolicyViolation(
                        rule_id="COMP-006", rule_name="Short Retention Period",
                        severity=DriftSeverity.MEDIUM,
                        resource_address=address, resource_type="*",
                        attribute=key, actual_value=val,
                        expected_condition="Log retention should be >= 90 days",
                        message=f"Log retention is {days} days — most frameworks require >= 90",
                    )
            except (ValueError, TypeError):
                pass
    return None


def _check_network_segmentation(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check network segmentation (PCI-DSS requirement)."""
    subnets = attrs.get("subnet_ids", attrs.get("subnets", []))
    if isinstance(subnets, list) and len(subnets) < 2:
        return PolicyViolation(
            rule_id="COMP-007", rule_name="Poor Network Segmentation",
            severity=DriftSeverity.LOW,
            resource_address=address, resource_type="network",
            attribute="subnets", actual_value=len(subnets),
            expected_condition="Use multiple subnets for network segmentation",
            message=f"Only {len(subnets)} subnet(s) — consider network segmentation",
        )
    return None


def _check_password_policy(address: str, attrs: dict[str, Any]) -> Optional[PolicyViolation]:
    """Check password policy settings."""
    min_length = attrs.get("minimum_password_length", attrs.get("password_policy.min_length"))
    if min_length is not None:
        try:
            length = int(str(min_length))
            if length < 14:
                return PolicyViolation(
                    rule_id="COMP-008", rule_name="Weak Password Policy",
                    severity=DriftSeverity.MEDIUM,
                    resource_address=address, resource_type="iam",
                    attribute="minimum_password_length", actual_value=min_length,
                    expected_condition="Minimum password length >= 14 characters",
                    message=f"Password minimum length is {length} — should be >= 14",
                )
        except (ValueError, TypeError):
            pass
    return None


# All compliance rules
COMPLIANCE_RULES: list[SecurityRule] = [
    SecurityRule("COMP-001", "Data Residency", "Resource in non-approved region", DriftSeverity.HIGH,
                 ["*"], _check_data_residency),
    SecurityRule("COMP-002", "Encryption at Rest", "Missing encryption at rest", DriftSeverity.CRITICAL,
                 ["*"], _check_encryption_at_rest),
    SecurityRule("COMP-003", "Audit Logging", "Missing audit logging", DriftSeverity.HIGH,
                 ["*"], _check_audit_logging),
    SecurityRule("COMP-004", "MFA Delete", "S3 MFA delete not enabled", DriftSeverity.MEDIUM,
                 ["aws_s3_bucket"], _check_mfa_delete),
    SecurityRule("COMP-005", "Access Logging", "Access logging disabled", DriftSeverity.MEDIUM,
                 ["aws_lb", "aws_s3_bucket", "azurerm_storage_account"], _check_access_logging),
    SecurityRule("COMP-006", "Retention Period", "Log retention too short", DriftSeverity.MEDIUM,
                 ["aws_cloudwatch_log_group", "azurerm_log_analytics_workspace"], _check_retention_period),
    SecurityRule("COMP-007", "Network Segmentation", "Insufficient segmentation", DriftSeverity.LOW,
                 ["aws_vpc", "azurerm_virtual_network", "google_compute_network"], _check_network_segmentation),
    SecurityRule("COMP-008", "Password Policy", "Weak password requirements", DriftSeverity.MEDIUM,
                 ["aws_iam_account_password_policy"], _check_password_policy),
]


class CompliancePolicyEngine:
    """Evaluate compliance policies against resource attributes."""

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

    def evaluate(
        self,
        resource_address: str,
        resource_type: str,
        attributes: dict[str, Any],
    ) -> list[PolicyViolation]:
        """Evaluate all enabled compliance rules."""
        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("Compliance rule %s failed: %s", rule.id, e)
        return violations
