"""Parse Terraform state files (.tfstate) into a normalized resource map."""

from __future__ import annotations

import json
import re
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any, Optional


@dataclass
class TerraformResource:
    """A resource extracted from Terraform state."""
    address: str  # e.g. azurerm_resource_group.main
    type: str  # e.g. azurerm_resource_group
    name: str  # e.g. main
    provider: str  # e.g. provider["registry.terraform.io/hashicorp/azurerm"]
    cloud_provider: str  # normalized: azure, aws, gcp
    resource_id: str  # cloud resource ID
    attributes: dict[str, Any] = field(default_factory=dict)
    dependencies: list[str] = field(default_factory=list)
    tainted: bool = False
    module: Optional[str] = None

    @property
    def display_name(self) -> str:
        """Human-readable name from attributes or address."""
        return self.attributes.get("name", self.attributes.get("tags", {}).get("Name", self.address))


# Maps Terraform resource type prefixes to cloud providers
_PROVIDER_MAP: dict[str, str] = {
    "azurerm_": "azure",
    "azuread_": "azure",
    "aws_": "aws",
    "google_": "gcp",
    "gcp_": "gcp",
}

# Attributes that are volatile and should be excluded from drift comparison
_VOLATILE_ATTRIBUTES: set[str] = {
    "id",
    "arn",
    "self_link",
    "etag",
    "creation_date",
    "created_at",
    "updated_at",
    "last_modified",
    "unique_id",
    "fingerprint",
    "generation",
    "metadata_fingerprint",
    "label_fingerprint",
    "status",
    "state",
    "terraform_labels",
    "effective_labels",
    "timeouts",
}

# Attributes containing sensitive data — track presence but not value
_SENSITIVE_ATTRIBUTES: set[str] = {
    "password",
    "secret",
    "private_key",
    "access_key",
    "secret_key",
    "connection_string",
    "primary_access_key",
    "secondary_access_key",
    "admin_password",
    "client_secret",
}


def _detect_cloud_provider(resource_type: str, provider_str: str) -> str:
    """Detect cloud provider from resource type or provider string."""
    for prefix, cloud in _PROVIDER_MAP.items():
        if resource_type.startswith(prefix):
            return cloud
    if "azurerm" in provider_str or "azuread" in provider_str:
        return "azure"
    if "aws" in provider_str:
        return "aws"
    if "google" in provider_str:
        return "gcp"
    return "unknown"


def _flatten_attributes(attrs: dict[str, Any], prefix: str = "") -> dict[str, Any]:
    """Flatten nested attributes with dot notation.

    Example: {"tags": {"env": "prod"}} -> {"tags.env": "prod"}
    """
    result: dict[str, Any] = {}
    for key, value in attrs.items():
        full_key = f"{prefix}.{key}" if prefix else key
        # Skip volatile attributes
        base_key = key.split(".")[-1] if "." in key else key
        if base_key in _VOLATILE_ATTRIBUTES:
            continue
        # Mask sensitive attributes
        if base_key in _SENSITIVE_ATTRIBUTES:
            result[full_key] = "<sensitive>"
            continue
        if isinstance(value, dict):
            result.update(_flatten_attributes(value, full_key))
        elif isinstance(value, list):
            result[full_key] = value
        else:
            result[full_key] = value
    return result


class TerraformStateParser:
    """Parse Terraform state files into normalized resource representations.

    Supports Terraform state format version 3 and 4.
    """

    def __init__(self) -> None:
        self.resources: dict[str, TerraformResource] = {}
        self.state_version: int = 0
        self.terraform_version: str = ""
        self.serial: int = 0

    def parse_file(self, path: str | Path) -> dict[str, TerraformResource]:
        """Parse a .tfstate file from disk.

        Args:
            path: Path to the .tfstate file.

        Returns:
            Dictionary mapping resource address to TerraformResource.

        Raises:
            FileNotFoundError: If the state file doesn't exist.
            ValueError: If the state file is malformed.
        """
        path = Path(path)
        if not path.exists():
            raise FileNotFoundError(f"Terraform state file not found: {path}")
        with open(path, "r", encoding="utf-8") as f:
            data = json.load(f)
        return self.parse(data)

    def parse(self, state: dict[str, Any]) -> dict[str, TerraformResource]:
        """Parse Terraform state data (already loaded JSON).

        Args:
            state: Parsed JSON from a .tfstate file.

        Returns:
            Dictionary mapping resource address to TerraformResource.
        """
        self.resources = {}
        self.state_version = state.get("version", 0)
        self.terraform_version = state.get("terraform_version", "")
        self.serial = state.get("serial", 0)

        if self.state_version < 3:
            raise ValueError(f"Unsupported Terraform state version: {self.state_version}. Need >= 3.")

        # Version 3: resources under "modules" -> "resources"
        # Version 4: resources at top level "resources" array
        if self.state_version == 3:
            self._parse_v3(state)
        else:
            self._parse_v4(state)

        return self.resources

    def _parse_v3(self, state: dict[str, Any]) -> None:
        """Parse Terraform state version 3 (< 0.12)."""
        modules = state.get("modules", [])
        for module in modules:
            module_path = module.get("path", [])
            module_prefix = ".".join(module_path[1:]) if len(module_path) > 1 else ""
            resources = module.get("resources", {})
            for addr, res_data in resources.items():
                res_type = res_data.get("type", "")
                provider = res_data.get("provider", "")
                primary = res_data.get("primary", {})
                attributes = primary.get("attributes", {})
                resource_id = primary.get("id", attributes.get("id", ""))
                full_addr = f"module.{module_prefix}.{addr}" if module_prefix else addr
                # Parse name from address: type.name
                name = addr.split(".")[-1] if "." in addr else addr
                resource = TerraformResource(
                    address=full_addr,
                    type=res_type,
                    name=name,
                    provider=provider,
                    cloud_provider=_detect_cloud_provider(res_type, provider),
                    resource_id=resource_id,
                    attributes=_flatten_attributes(attributes),
                    tainted=res_data.get("tainted", False),
                    module=module_prefix or None,
                )
                self.resources[full_addr] = resource

    def _parse_v4(self, state: dict[str, Any]) -> None:
        """Parse Terraform state version 4 (>= 0.12)."""
        resources = state.get("resources", [])
        for res_block in resources:
            res_mode = res_block.get("mode", "managed")
            if res_mode == "data":
                continue  # Skip data sources
            res_type = res_block.get("type", "")
            res_name = res_block.get("name", "")
            provider = res_block.get("provider", "")
            module = res_block.get("module", "")
            instances = res_block.get("instances", [])

            for idx, instance in enumerate(instances):
                attrs = instance.get("attributes", {})
                index_key = instance.get("index_key")
                # Build address
                base_addr = f"{res_type}.{res_name}"
                if module:
                    base_addr = f"{module}.{base_addr}"
                if index_key is not None:
                    if isinstance(index_key, int):
                        base_addr = f"{base_addr}[{index_key}]"
                    else:
                        base_addr = f'{base_addr}["{index_key}"]'
                elif len(instances) > 1:
                    base_addr = f"{base_addr}[{idx}]"

                resource_id = attrs.get("id", "")
                deps = instance.get("dependencies", [])
                is_tainted = instance.get("status") == "tainted"

                resource = TerraformResource(
                    address=base_addr,
                    type=res_type,
                    name=res_name,
                    provider=provider,
                    cloud_provider=_detect_cloud_provider(res_type, provider),
                    resource_id=resource_id,
                    attributes=_flatten_attributes(attrs),
                    dependencies=deps,
                    tainted=is_tainted,
                    module=module or None,
                )
                self.resources[base_addr] = resource

    def get_resources_by_provider(self, cloud_provider: str) -> dict[str, TerraformResource]:
        """Filter resources by cloud provider."""
        return {
            addr: res for addr, res in self.resources.items()
            if res.cloud_provider == cloud_provider
        }

    def get_resources_by_type(self, resource_type: str) -> dict[str, TerraformResource]:
        """Filter resources by Terraform resource type."""
        return {
            addr: res for addr, res in self.resources.items()
            if res.type == resource_type
        }

    def get_security_relevant_attributes(self, resource: TerraformResource) -> dict[str, Any]:
        """Extract security-relevant attributes from a resource.

        These are attributes that, if changed, indicate a potential security drift.
        """
        security_patterns = [
            r".*public.*",
            r".*ingress.*",
            r".*egress.*",
            r".*firewall.*",
            r".*security_group.*",
            r".*acl.*",
            r".*encryption.*",
            r".*ssl.*",
            r".*tls.*",
            r".*kms.*",
            r".*iam.*",
            r".*policy.*",
            r".*role.*",
            r".*permission.*",
            r".*access.*",
            r".*auth.*",
            r".*logging.*",
            r".*monitoring.*",
            r".*network_rule.*",
            r".*ip_restriction.*",
        ]
        result: dict[str, Any] = {}
        for key, value in resource.attributes.items():
            for pattern in security_patterns:
                if re.match(pattern, key, re.IGNORECASE):
                    result[key] = value
                    break
        return result
