"""Generate IaC remediation code to fix detected drift.

Produces Terraform HCL or Pulumi Python code that will bring
the actual state back in line with the desired state.
"""

from __future__ import annotations

import json
import textwrap
from dataclasses import dataclass, field
from typing import Any, Optional

from .differ import AttributeDiff, ResourceDiff


@dataclass
class RemediationPlan:
    """A plan to remediate detected drift."""
    resource_address: str
    resource_type: str
    iac_format: str  # "terraform" or "pulumi"
    code: str
    description: str
    risk_level: str  # "low", "medium", "high"
    requires_import: bool = False
    import_command: Optional[str] = None
    destroy_required: bool = False
    attribute_fixes: list[str] = field(default_factory=list)

    def to_dict(self) -> dict[str, Any]:
        return {
            "resource_address": self.resource_address,
            "resource_type": self.resource_type,
            "iac_format": self.iac_format,
            "code": self.code,
            "description": self.description,
            "risk_level": self.risk_level,
            "requires_import": self.requires_import,
            "import_command": self.import_command,
            "destroy_required": self.destroy_required,
            "attribute_fixes": self.attribute_fixes,
        }


def _tf_value(value: Any) -> str:
    """Convert a Python value to Terraform HCL syntax."""
    if value is None:
        return "null"
    if isinstance(value, bool):
        return "true" if value else "false"
    if isinstance(value, (int, float)):
        return str(value)
    if isinstance(value, str):
        return f'"{value}"'
    if isinstance(value, list):
        items = ", ".join(_tf_value(v) for v in value)
        return f"[{items}]"
    if isinstance(value, dict):
        items = "\n".join(f"    {k} = {_tf_value(v)}" for k, v in value.items())
        return f"{{\n{items}\n  }}"
    return f'"{value}"'


def _py_value(value: Any) -> str:
    """Convert a Python value to Pulumi Python syntax."""
    if value is None:
        return "None"
    if isinstance(value, bool):
        return "True" if value else "False"
    if isinstance(value, str):
        return f'"{value}"'
    return repr(value)


# Resource type to Terraform resource name mapping
_TF_RESOURCE_NAMES: dict[str, str] = {
    "azurerm_resource_group": "azurerm_resource_group",
    "azurerm_storage_account": "azurerm_storage_account",
    "azurerm_storage_container": "azurerm_storage_container",
    "azurerm_network_security_group": "azurerm_network_security_group",
    "azurerm_virtual_network": "azurerm_virtual_network",
    "azurerm_virtual_machine": "azurerm_virtual_machine",
    "azurerm_key_vault": "azurerm_key_vault",
    "aws_instance": "aws_instance",
    "aws_security_group": "aws_security_group",
    "aws_s3_bucket": "aws_s3_bucket",
    "aws_db_instance": "aws_db_instance",
    "aws_iam_role": "aws_iam_role",
    "google_compute_instance": "google_compute_instance",
    "google_storage_bucket": "google_storage_bucket",
    "google_compute_firewall": "google_compute_firewall",
}


class RemediationEngine:
    """Generate IaC remediation code for detected drift.

    Supports Terraform HCL and Pulumi Python output formats.
    """

    def generate(
        self,
        resource_diff: ResourceDiff,
        iac_format: str = "terraform",
    ) -> RemediationPlan:
        """Generate remediation code for a single resource drift.

        Args:
            resource_diff: The detected drift to remediate.
            iac_format: Output format — "terraform" or "pulumi".

        Returns:
            RemediationPlan with generated code and metadata.
        """
        if resource_diff.diff_type == "deleted":
            return self._remediate_deleted(resource_diff, iac_format)
        elif resource_diff.diff_type == "unmanaged":
            return self._remediate_unmanaged(resource_diff, iac_format)
        else:
            return self._remediate_modified(resource_diff, iac_format)

    def _remediate_modified(self, diff: ResourceDiff, fmt: str) -> RemediationPlan:
        """Generate fix for modified resources — revert attributes to desired state."""
        attribute_fixes: list[str] = []
        if fmt == "terraform":
            code = self._gen_terraform_fix(diff, attribute_fixes)
        else:
            code = self._gen_pulumi_fix(diff, attribute_fixes)

        risk = "low"
        if diff.has_security_drift:
            risk = "high"
        elif diff.has_cost_drift:
            risk = "medium"

        return RemediationPlan(
            resource_address=diff.resource_address,
            resource_type=diff.resource_type,
            iac_format=fmt,
            code=code,
            description=f"Revert {len(diff.attribute_diffs)} drifted attribute(s) on {diff.resource_name}",
            risk_level=risk,
            attribute_fixes=attribute_fixes,
        )

    def _remediate_deleted(self, diff: ResourceDiff, fmt: str) -> RemediationPlan:
        """Generate fix for resources deleted from cloud."""
        if fmt == "terraform":
            code = textwrap.dedent(f"""\
                # Resource {diff.resource_address} was deleted from cloud.
                # Option 1: Re-apply Terraform to recreate it.
                # Run: terraform apply -target="{diff.resource_address}"

                # Option 2: Remove from state if intentionally deleted.
                # Run: terraform state rm "{diff.resource_address}"
            """)
        else:
            code = textwrap.dedent(f"""\
                # Resource {diff.resource_address} was deleted from cloud.
                # Option 1: Run `pulumi up` to recreate it.
                # Option 2: Remove from state:
                #   pulumi state delete '{diff.resource_address}'
            """)

        return RemediationPlan(
            resource_address=diff.resource_address,
            resource_type=diff.resource_type,
            iac_format=fmt,
            code=code,
            description=f"Resource {diff.resource_name} deleted from cloud — recreate or remove from state",
            risk_level="high",
            destroy_required=False,
        )

    def _remediate_unmanaged(self, diff: ResourceDiff, fmt: str) -> RemediationPlan:
        """Generate fix for unmanaged cloud resources."""
        tf_type = _TF_RESOURCE_NAMES.get(diff.resource_type, diff.resource_type)
        name = diff.resource_name.replace("-", "_").replace(".", "_")

        if fmt == "terraform":
            code = textwrap.dedent(f"""\
                # Unmanaged resource detected: {diff.resource_id}
                # Import into Terraform state:

                resource "{tf_type}" "{name}" {{
                  # TODO: Fill in required attributes after import
                  # Run: terraform import {tf_type}.{name} {diff.resource_id}
                }}
            """)
            import_cmd = f'terraform import {tf_type}.{name} {diff.resource_id}'
        else:
            code = textwrap.dedent(f"""\
                # Unmanaged resource detected: {diff.resource_id}
                # Import into Pulumi state:

                import pulumi

                {name} = pulumi.import_(
                    "{diff.resource_type}",
                    "{name}",
                    "{diff.resource_id}",
                )
            """)
            import_cmd = f'pulumi import {diff.resource_type} {name} {diff.resource_id}'

        return RemediationPlan(
            resource_address=diff.resource_address,
            resource_type=diff.resource_type,
            iac_format=fmt,
            code=code,
            description=f"Import unmanaged resource {diff.resource_name} into IaC",
            risk_level="medium",
            requires_import=True,
            import_command=import_cmd,
        )

    def _gen_terraform_fix(self, diff: ResourceDiff, fixes: list[str]) -> str:
        """Generate Terraform HCL that reverts drifted attributes."""
        tf_type = _TF_RESOURCE_NAMES.get(diff.resource_type, diff.resource_type)
        # Parse name from address: type.name or module.x.type.name
        parts = diff.resource_address.split(".")
        name = parts[-1] if len(parts) >= 2 else diff.resource_name.replace("-", "_")

        lines = [
            f'# Fix drift on {diff.resource_address}',
            f'# {len(diff.attribute_diffs)} attribute(s) drifted',
            f'',
            f'resource "{tf_type}" "{name}" {{',
        ]

        # Group attributes by nesting level
        nested: dict[str, dict[str, Any]] = {}
        flat: dict[str, Any] = {}

        for attr_diff in diff.attribute_diffs:
            if attr_diff.diff_type == "removed":
                # Attribute was removed from cloud — re-add with desired value
                fix_msg = f"  Re-add: {attr_diff.attribute} = {attr_diff.desired}"
            elif attr_diff.diff_type == "added":
                # Attribute was added in cloud — Terraform will revert on apply
                fix_msg = f"  Remove added: {attr_diff.attribute}"
            else:
                fix_msg = f"  Revert: {attr_diff.attribute} from {attr_diff.actual} -> {attr_diff.desired}"
            fixes.append(fix_msg)

            # Only emit desired values (revert to IaC state)
            if attr_diff.desired is not None and attr_diff.diff_type != "added":
                parts = attr_diff.attribute.split(".")
                if len(parts) > 1:
                    block = parts[0]
                    attr = ".".join(parts[1:])
                    nested.setdefault(block, {})[attr] = attr_diff.desired
                else:
                    flat[attr_diff.attribute] = attr_diff.desired

        for attr, value in sorted(flat.items()):
            lines.append(f"  {attr} = {_tf_value(value)}")

        for block, attrs in sorted(nested.items()):
            lines.append(f"  {block} {{")
            for attr, value in sorted(attrs.items()):
                lines.append(f"    {attr} = {_tf_value(value)}")
            lines.append("  }")

        lines.append("}")
        lines.append("")
        lines.append(f"# Apply: terraform apply -target='{tf_type}.{name}'")
        return "\n".join(lines)

    def _gen_pulumi_fix(self, diff: ResourceDiff, fixes: list[str]) -> str:
        """Generate Pulumi Python code that reverts drifted attributes."""
        name = diff.resource_name.replace("-", "_").replace(".", "_")

        lines = [
            f"# Fix drift on {diff.resource_address}",
            f"# {len(diff.attribute_diffs)} attribute(s) drifted",
            "",
            "import pulumi",
        ]

        # Determine Pulumi import
        if "azurerm" in diff.resource_type or "azure" in diff.resource_type.lower():
            lines.append("import pulumi_azure_native as azure")
        elif "aws" in diff.resource_type:
            lines.append("import pulumi_aws as aws")
        elif "google" in diff.resource_type:
            lines.append("import pulumi_gcp as gcp")

        lines.append("")
        lines.append(f"# Update the following attributes on '{name}':")

        for attr_diff in diff.attribute_diffs:
            if attr_diff.desired is not None and attr_diff.diff_type != "added":
                fix_msg = f"#   {attr_diff.attribute}: {attr_diff.actual} -> {attr_diff.desired}"
                fixes.append(fix_msg)
                lines.append(fix_msg)

        lines.append("")
        lines.append("# Run: pulumi up")

        return "\n".join(lines)

    def generate_batch(
        self,
        diffs: list[ResourceDiff],
        iac_format: str = "terraform",
    ) -> list[RemediationPlan]:
        """Generate remediation plans for multiple drifts."""
        return [self.generate(d, iac_format) for d in diffs]
