"""HCL-aware patch generation for compliance remediation."""

from __future__ import annotations

import difflib
import re
from datetime import UTC, datetime
from typing import TYPE_CHECKING, Any

from pydantic import BaseModel, ConfigDict

from complyform.core.remediator.import_generator import ImportGenerator

if TYPE_CHECKING:
    from complyform.core.assessor.control_matcher import Finding
    from complyform.core.profiles.models import ResourceMapping
    from complyform.core.scanner.state_parser import ResourceRecord


class PatchFile(BaseModel):
    """Single generated patch file."""

    model_config = ConfigDict(strict=True)

    resource_address: str
    control_id: str
    requirement_id: str | None
    resource_type: str
    filename: str
    content: str
    diff: str
    strategy: str  # "state_only" | "source_modify"
    dependencies: list[str]
    risk_level: str  # "low" | "medium" | "high"


def _sanitize_filename(name: str) -> str:
    """Sanitize a string for use in filenames."""
    return re.sub(r"[^\w\-.]", "_", name)


def _resource_name_from_address(address: str) -> str:
    """Extract the resource name (last segment) from address."""
    return address.rsplit(".", 1)[-1]


def _render_hcl_value(value: Any, indent: int = 2) -> str:
    """Render a Python value as HCL."""
    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(_render_hcl_value(v) for v in value)
        return f"[{items}]"
    if isinstance(value, dict):
        lines = []
        prefix = " " * indent
        lines.append("{")
        for k, v in value.items():
            lines.append(f"{prefix}  {k} = {_render_hcl_value(v, indent + 2)}")
        lines.append(f"{prefix}}}")
        return "\n".join(lines)
    return f'"{value}"'


def _merge_attributes(
    base: dict[str, Any],
    required: dict[str, Any],
) -> dict[str, Any]:
    """Deep merge required_config into base attributes."""
    result = dict(base)
    for key, value in required.items():
        if isinstance(value, dict) and isinstance(result.get(key), dict):
            result[key] = _merge_attributes(result[key], value)
        else:
            result[key] = value
    return result


def _render_resource_block(
    resource_type: str,
    name: str,
    attrs: dict[str, Any],
    control_id: str,
) -> str:
    """Render an HCL resource block from attributes."""
    lines = [f'resource "{resource_type}" "{name}" {{']
    for key, value in sorted(attrs.items()):
        if key in ("id", "self_link", "etag", "terraform_labels"):
            continue
        if isinstance(value, dict):
            lines.append(f"  {key} {{")
            for sk, sv in sorted(value.items()):
                lines.append(f"    {sk} = {_render_hcl_value(sv, 4)}")
            lines.append("  }")
        elif isinstance(value, list) and value and isinstance(value[0], dict):
            for item in value:
                lines.append(f"  {key} {{")
                for sk, sv in sorted(item.items()):
                    lines.append(f"    {sk} = {_render_hcl_value(sv, 4)}")
                lines.append("  }")
        else:
            comment = ""
            lines.append(f"  {key} = {_render_hcl_value(value)}{comment}")
    lines.append("}")
    return "\n".join(lines)


def _render_dependency_block(
    dep_type: str,
    dep_name: str,
    template: str,
    control_id: str,
) -> str:
    """Render a dependency resource block."""
    lines = [
        f"# Required by {control_id}",
        f'resource "{dep_type}" "{dep_name}" {{',
    ]
    for line in template.strip().splitlines():
        lines.append(f"  {line}")
    lines.append("}")
    return "\n".join(lines)


class PatchGenerator:
    """Generate Terraform patch files for compliance remediation."""

    def __init__(self) -> None:
        self._import_gen = ImportGenerator()

    def generate_patches(
        self,
        findings: list[Finding],
        mappings: dict[str, ResourceMapping],
        resources: list[ResourceRecord],
        cloud: str,
    ) -> list[PatchFile]:
        """Generate Terraform patch files for all failing findings."""
        resource_map: dict[str, ResourceRecord] = {r.address: r for r in resources}

        # Group findings by resource address to consolidate
        by_resource: dict[str, list[tuple[Finding, ResourceMapping]]] = {}
        for f in findings:
            if f.status != "FAIL":
                continue
            key = f"{f.control_id}:{f.resource_type}"
            mapping = mappings.get(key)
            if mapping is None:
                continue
            by_resource.setdefault(f.resource_address, []).append((f, mapping))

        patches: list[PatchFile] = []
        seen_deps: set[str] = set()

        for addr, finding_mappings in by_resource.items():
            resource = resource_map.get(addr)
            if resource is None:
                continue

            for finding, mapping in finding_mappings:
                patch = self._generate_state_only_patch(
                    finding=finding,
                    mapping=mapping,
                    resource=resource,
                    cloud=cloud,
                    seen_deps=seen_deps,
                )
                patches.append(patch)

        return patches

    def _generate_state_only_patch(
        self,
        *,
        finding: Finding,
        mapping: ResourceMapping,
        resource: ResourceRecord,
        cloud: str,
        seen_deps: set[str],
    ) -> PatchFile:
        """Generate a state-only patch (import block + compliant def)."""
        now = datetime.now(UTC).strftime("%Y-%m-%dT%H:%M:%SZ")
        res_name = _resource_name_from_address(resource.address)

        import_block = self._import_gen.generate_import_block(resource, cloud)

        required = mapping.required_config or {}
        merged = _merge_attributes(resource.attributes, required)

        resource_block = _render_resource_block(
            resource.resource_type,
            res_name,
            merged,
            finding.control_id,
        )

        dep_blocks: list[str] = []
        dep_addresses: list[str] = []
        max_risk = "low"
        risk_order = {"low": 0, "medium": 1, "high": 2}

        for dep in mapping.dependencies:
            dep_key = f"{dep.resource_type}.{finding.control_id}"
            if dep_key in seen_deps:
                continue
            seen_deps.add(dep_key)
            dep_name = f"{res_name}_{dep.resource_type.split('_')[-1]}"
            dep_blocks.append(
                _render_dependency_block(
                    dep.resource_type,
                    dep_name,
                    dep.template,
                    finding.control_id,
                )
            )
            dep_addresses.append(f"{dep.resource_type}.{dep_name}")
            if risk_order.get(dep.risk_level, 0) > risk_order.get(max_risk, 0):
                max_risk = dep.risk_level

        header = (
            f"# ComplyForm remediation patch\n"
            f"# Control: {finding.control_id}"
            f" — {finding.finding}\n"
            f"# Resource: {resource.address}\n"
            f"# Strategy: state-only"
            f" (import block + compliant definition)\n"
            f"# Generated: {now}\n"
            f"#\n"
            f"# IMPORTANT: Review this patch before applying.\n"
            f"# Run: tofu plan / terraform plan"
            f" to preview changes.\n"
        )

        parts = [header, import_block, "", resource_block]
        if dep_blocks:
            parts.append("")
            parts.extend(dep_blocks)
        content = "\n".join(parts) + "\n"

        diff = "".join(
            difflib.unified_diff(
                [],
                content.splitlines(keepends=True),
                fromfile="/dev/null",
                tofile=f"patches/{_sanitize_filename(resource.resource_type)}_{_sanitize_filename(res_name)}_{_sanitize_filename(finding.control_id)}.tf",
            )
        )

        filename = (
            f"{_sanitize_filename(resource.resource_type)}"
            f"_{_sanitize_filename(res_name)}"
            f"_{_sanitize_filename(finding.control_id)}.tf"
        )

        return PatchFile(
            resource_address=resource.address,
            control_id=finding.control_id,
            requirement_id=mapping.requirement_id,
            resource_type=resource.resource_type,
            filename=filename,
            content=content,
            diff=diff,
            strategy="state_only",
            dependencies=dep_addresses,
            risk_level=max_risk,
        )
