"""Jinja2 template rendering engine for greenfield Terraform generation.

Renders scaffolding templates (_-prefixed, always generated) and
resource-specific templates (generated per selected service).
All resources go into a single main.tf, dependencies before dependents.
"""

from __future__ import annotations

import subprocess
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import TYPE_CHECKING, Any

import jinja2

from complyform.core.errors import TemplateNotFoundError, TemplateRenderError

if TYPE_CHECKING:
    from complyform.core.greenfield.dependency_resolver import DependencySpec
    from complyform.core.greenfield.service_catalog import ServiceDefinition

_TEMPLATES_DIR = (
    Path(__file__).resolve().parent.parent.parent.parent.parent / "templates"
)


@dataclass(frozen=True)
class GenerateContext:
    """Context passed to every Jinja2 template."""

    project_id: str
    cloud: str
    org_id: str | None
    region: str
    frameworks: list[str]
    services: list[ServiceDefinition]
    merged_configs: dict[str, dict[str, Any]]  # resource_type → merged required_config
    dependencies: list[DependencySpec]
    compliance_comments: dict[str, list[str]]  # resource_type → ["SOC2 CC6.1", ...]


def render_terraform(
    context: GenerateContext,
    output_dir: Path,
) -> list[Path]:
    """Render Jinja2 templates into .tf files.

    Always generates: providers.tf, variables.tf, outputs.tf, backend.tf,
    README.md, terraform.tfvars.example.
    Per-service: resource blocks in main.tf.
    Per-dependency: dependency resources in main.tf (before dependents).

    Returns list of generated file paths.

    Post-render: runs <iac_binary> fmt on output directory.
    If iac binary not found, skip formatting with a warning to stderr.
    """
    output_dir.mkdir(parents=True, exist_ok=True)

    cloud = context.cloud
    template_dir = _TEMPLATES_DIR / cloud
    if not template_dir.exists():
        raise TemplateNotFoundError(f"{cloud}/", cloud)

    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(str(template_dir)),
        keep_trailing_newline=True,
        trim_blocks=True,
        lstrip_blocks=True,
        undefined=jinja2.StrictUndefined,
    )

    template_vars = _build_template_vars(context)
    generated: list[Path] = []

    # 1. Scaffolding templates (_-prefixed)
    scaffolding_map = {
        "_providers.tf.j2": "providers.tf",
        "_variables.tf.j2": "variables.tf",
        "_outputs.tf.j2": "outputs.tf",
        "_backend.tf.j2": "backend.tf",
        "_README.md.j2": "README.md",
    }
    for template_name, output_name in scaffolding_map.items():
        path = _render_template(
            env,
            template_name,
            template_vars,
            output_dir,
            output_name,
            cloud,
        )
        generated.append(path)

    # 2. Generate terraform.tfvars.example
    tfvars_path = output_dir / "terraform.tfvars.example"
    tfvars_path.write_text(
        _generate_tfvars_example(context),
        encoding="utf-8",
    )
    generated.append(tfvars_path)

    # 3. Resource templates → main.tf
    main_tf_blocks: list[str] = []

    # Dependencies first (topological order)
    for dep in context.dependencies:
        template_name = f"{dep.resource_type}.tf.j2"
        if _template_exists(template_dir, template_name):
            rendered = _render_template_string(
                env,
                template_name,
                template_vars,
                cloud,
            )
            main_tf_blocks.append(rendered)

    # Then service resources
    for service in context.services:
        for resource_type in service.resource_types:
            template_name = f"{resource_type}.tf.j2"
            if _template_exists(template_dir, template_name):
                rendered = _render_template_string(
                    env,
                    template_name,
                    template_vars,
                    cloud,
                )
                main_tf_blocks.append(rendered)

    if main_tf_blocks:
        main_tf_path = output_dir / "main.tf"
        main_tf_path.write_text(
            "\n".join(main_tf_blocks),
            encoding="utf-8",
        )
        generated.append(main_tf_path)

    # 4. Run iac binary fmt
    _run_fmt(output_dir)

    return generated


def _build_template_vars(context: GenerateContext) -> dict[str, Any]:
    """Build the template variable dict from GenerateContext."""
    return {
        "project_id": context.project_id,
        "cloud": context.cloud,
        "org_id": context.org_id,
        "region": context.region,
        "frameworks": context.frameworks,
        "services": context.services,
        "merged_configs": context.merged_configs,
        "dependencies": context.dependencies,
        "compliance_comments": context.compliance_comments,
        "service_ids": [s.id for s in context.services],
    }


def _template_exists(template_dir: Path, template_name: str) -> bool:
    """Check if a template file exists."""
    return (template_dir / template_name).exists()


def _render_template(
    env: jinja2.Environment,
    template_name: str,
    variables: dict[str, Any],
    output_dir: Path,
    output_name: str,
    cloud: str,
) -> Path:
    """Render a template to a file."""
    try:
        tmpl = env.get_template(template_name)
    except jinja2.TemplateNotFound:
        raise TemplateNotFoundError(template_name, cloud) from None
    try:
        content = tmpl.render(**variables)
    except jinja2.TemplateError as exc:
        raise TemplateRenderError(template_name, str(exc)) from exc

    path = output_dir / output_name
    path.write_text(content, encoding="utf-8")
    return path


def _render_template_string(
    env: jinja2.Environment,
    template_name: str,
    variables: dict[str, Any],
    cloud: str,
) -> str:
    """Render a template to a string."""
    try:
        tmpl = env.get_template(template_name)
    except jinja2.TemplateNotFound:
        raise TemplateNotFoundError(template_name, cloud) from None
    try:
        return tmpl.render(**variables)
    except jinja2.TemplateError as exc:
        raise TemplateRenderError(template_name, str(exc)) from exc


def _generate_tfvars_example(context: GenerateContext) -> str:
    """Generate a terraform.tfvars.example file."""
    lines = [
        "# terraform.tfvars.example",
        "# Copy to terraform.tfvars and fill in actual values.",
        "",
        f'project_id = "{context.project_id}"',
        f'region     = "{context.region}"',
    ]
    if context.org_id:
        lines.append(f'org_id     = "{context.org_id}"')
    lines.append("")
    return "\n".join(lines)


def _run_fmt(output_dir: Path) -> None:
    """Run iac binary fmt on the output directory. Warn on failure."""
    try:
        from complyform.core.iac_binary import resolve_iac_binary

        binary = resolve_iac_binary()
        subprocess.run(
            [binary.path, "fmt", str(output_dir)],
            capture_output=True,
            timeout=30,
        )
    except Exception:
        # Not an error — just skip formatting
        print(  # noqa: T201
            "Warning: Could not run IaC binary fmt. "
            "Install tofu or terraform for auto-formatting.",
            file=sys.stderr,
        )
