//! IaC Template generator: renders Terraform, Ansible, and Docker Compose templates.

use crate::error::Result;
use crate::models::ProjectSpec;
use crate::template::TemplateLoader;
use std::path::Path;
use tera::Context;

/// Generates Infrastructure as Code templates from domain features.
pub struct IacTemplateGenerator;

impl IacTemplateGenerator {
    /// Generate IaC templates for all configured providers.
    pub fn generate(
        spec: &ProjectSpec,
        output_dir: impl AsRef<Path>,
        template_loader: &TemplateLoader,
    ) -> Result<()> {
        let output_dir = output_dir.as_ref();
        tracing::info!("Generating IaC templates for project: {}", spec.name);

        // Ensure iac directory exists
        let iac_dir = output_dir.join("iac");
        std::fs::create_dir_all(&iac_dir).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to create iac directory: {}",
                e
            ))
        })?;

        // Prepare template context
        let mut context = Context::new();
        context.insert("project_name", &spec.name);
        context.insert("features", &spec.domain_features);
        context.insert("infrastructure", &spec.infrastructure);

        // Generate Terraform/OpenTofu templates
        if spec.iac_templates.terraform_enabled {
            Self::generate_terraform_templates(spec, &iac_dir, template_loader, &context)?;
        }

        // Generate Ansible templates
        if spec.iac_templates.ansible_enabled {
            Self::generate_ansible_templates(spec, &iac_dir, template_loader, &context)?;
        }

        // Generate Docker Compose templates
        if spec.iac_templates.docker_compose_enabled {
            Self::generate_docker_compose_templates(spec, &iac_dir, template_loader, &context)?;
        }

        // Generate README
        Self::generate_iac_readme(spec, &iac_dir)?;

        tracing::info!("Successfully generated IaC templates");
        Ok(())
    }

    /// Generate Terraform/OpenTofu templates.
    fn generate_terraform_templates(
        spec: &ProjectSpec,
        iac_dir: &Path,
        template_loader: &TemplateLoader,
        context: &Context,
    ) -> Result<()> {
        let tofu_dir = iac_dir.join("tofu");
        std::fs::create_dir_all(&tofu_dir).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to create tofu directory: {}",
                e
            ))
        })?;

        // Generate common Terraform configuration
        let common_tf = template_loader.render("iac/tofu/common/main.tf.tera", context)?;
        let common_dir = tofu_dir.join("common");
        std::fs::create_dir_all(&common_dir).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to create tofu/common directory: {}",
                e
            ))
        })?;
        std::fs::write(common_dir.join("main.tf"), common_tf).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write common/main.tf: {}",
                e
            ))
        })?;

        // Generate provider-specific configurations
        let providers = vec!["hetzner", "lxd"];
        for provider in providers {
            let provider_dir = tofu_dir.join(provider);
            std::fs::create_dir_all(&provider_dir).map_err(|e| {
                crate::error::ProvisioningGenError::Other(format!(
                    "Failed to create tofu/{} directory: {}",
                    provider, e
                ))
            })?;

            // Render main.tf for this provider
            let template_path = format!("iac/tofu/{}/main.tf.tera", provider);
            let provider_tf = template_loader.render(&template_path, context)?;
            std::fs::write(provider_dir.join("main.tf"), provider_tf).map_err(|e| {
                crate::error::ProvisioningGenError::Other(format!(
                    "Failed to write {}/main.tf: {}",
                    provider, e
                ))
            })?;

            // Generate variables.tf
            Self::generate_terraform_variables(&provider_dir, spec)?;

            // Generate outputs.tf
            Self::generate_terraform_outputs(&provider_dir, spec)?;
        }

        tracing::debug!("Generated Terraform templates");
        Ok(())
    }

    /// Generate Terraform variables.tf file.
    fn generate_terraform_variables(provider_dir: &Path, spec: &ProjectSpec) -> Result<()> {
        let mut variables = String::new();

        variables.push_str("# Terraform variables\n\n");

        // Generate variables for each domain feature
        for feature in &spec.domain_features {
            variables.push_str(&format!("# {} configuration\n", feature.name));

            for field in &feature.fields {
                variables.push_str(&format!(
                    "variable \"{}_{} \" {{\n  description = \"{}\"\n  type        = string\n",
                    feature.name, field.name, field.prompt
                ));

                if let Some(default) = &field.default {
                    variables.push_str(&format!("  default     = {}\n", default));
                }

                variables.push_str("}\n\n");
            }
        }

        std::fs::write(provider_dir.join("variables.tf"), variables).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write variables.tf: {}",
                e
            ))
        })?;

        Ok(())
    }

    /// Generate Terraform outputs.tf file.
    fn generate_terraform_outputs(provider_dir: &Path, spec: &ProjectSpec) -> Result<()> {
        let mut outputs = String::new();

        outputs.push_str("# Terraform outputs\n\n");

        outputs.push_str(&format!(
            "output \"{}_instance_ip\" {{\n  description = \"IP address of the {} instance\"\n  value       = \"placeholder\"\n}}\n\n",
            spec.name, spec.name
        ));

        outputs.push_str(&format!(
            "output \"{}_connection_string\" {{\n  description = \"Connection string for {}\"\n  value       = \"placeholder\"\n  sensitive   = true\n}}\n",
            spec.name, spec.name
        ));

        std::fs::write(provider_dir.join("outputs.tf"), outputs).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!("Failed to write outputs.tf: {}", e))
        })?;

        Ok(())
    }

    /// Generate Ansible templates.
    fn generate_ansible_templates(
        spec: &ProjectSpec,
        iac_dir: &Path,
        template_loader: &TemplateLoader,
        context: &Context,
    ) -> Result<()> {
        let ansible_dir = iac_dir.join("ansible");
        std::fs::create_dir_all(&ansible_dir).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to create ansible directory: {}",
                e
            ))
        })?;

        // Generate playbook
        let playbook = template_loader.render("iac/ansible/playbook.yml.tera", context)?;
        std::fs::write(ansible_dir.join("playbook.yml"), playbook).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write playbook.yml: {}",
                e
            ))
        })?;

        // Generate inventory
        let inventory = template_loader.render("iac/ansible/inventory.ini.tera", context)?;
        std::fs::write(ansible_dir.join("inventory.ini"), inventory).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write inventory.ini: {}",
                e
            ))
        })?;

        // Generate roles directory with basic role structure
        let roles_dir = ansible_dir.join("roles").join(&spec.name);
        std::fs::create_dir_all(roles_dir.join("tasks")).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to create roles/{}/tasks directory: {}",
                spec.name, e
            ))
        })?;

        // Generate tasks/main.yml
        let tasks_main = format!(
            "---\n# Tasks for {}\n\n- name: Deploy {} configuration\n  template:\n    src: config.j2\n    dest: /etc/{}/config.ncl\n",
            spec.name, spec.name, spec.name
        );
        std::fs::write(roles_dir.join("tasks/main.yml"), tasks_main).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write tasks/main.yml: {}",
                e
            ))
        })?;

        tracing::debug!("Generated Ansible templates");
        Ok(())
    }

    /// Generate Docker Compose templates.
    fn generate_docker_compose_templates(
        spec: &ProjectSpec,
        iac_dir: &Path,
        template_loader: &TemplateLoader,
        context: &Context,
    ) -> Result<()> {
        let docker_dir = iac_dir.join("docker");
        std::fs::create_dir_all(&docker_dir).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to create docker directory: {}",
                e
            ))
        })?;

        // Generate docker-compose.yml
        let compose = template_loader.render("iac/docker-compose/services.yml.tera", context)?;
        std::fs::write(docker_dir.join("docker-compose.yml"), compose).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!(
                "Failed to write docker-compose.yml: {}",
                e
            ))
        })?;

        // Generate monitoring stack if enabled
        if !spec.infrastructure.monitoring.is_empty() {
            let monitoring =
                template_loader.render("iac/docker-compose/monitoring.yml.tera", context)?;
            std::fs::write(docker_dir.join("docker-compose.monitoring.yml"), monitoring).map_err(
                |e| {
                    crate::error::ProvisioningGenError::Other(format!(
                        "Failed to write docker-compose.monitoring.yml: {}",
                        e
                    ))
                },
            )?;
        }

        // Generate Dockerfile
        Self::generate_dockerfile(&docker_dir, spec)?;

        tracing::debug!("Generated Docker Compose templates");
        Ok(())
    }

    /// Generate Dockerfile for the application.
    fn generate_dockerfile(docker_dir: &Path, spec: &ProjectSpec) -> Result<()> {
        let dockerfile = format!(
            "# Dockerfile for {}\n\
             FROM rust:1.75 as builder\n\
             \n\
             WORKDIR /app\n\
             COPY . .\n\
             RUN cargo build --release\n\
             \n\
             FROM debian:bookworm-slim\n\
             \n\
             # Install Nickel\n\
             RUN apt-get update && apt-get install -y curl \\\n\
                 && curl -L https://github.com/tweag/nickel/releases/download/1.3.0/nickel-1.3.0-x86_64-unknown-linux-gnu.tar.gz | tar xz -C /usr/local/bin \\\n\
                 && rm -rf /var/lib/apt/lists/*\n\
             \n\
             COPY --from=builder /app/target/release/{} /usr/local/bin/{}\n\
             COPY config.ncl /etc/{}/config.ncl\n\
             \n\
             EXPOSE 8080\n\
             \n\
             CMD [\"{}\", \"--config\", \"/etc/{}/config.ncl\"]\n",
            spec.name, spec.name, spec.name, spec.name, spec.name, spec.name
        );

        std::fs::write(docker_dir.join("Dockerfile"), dockerfile).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!("Failed to write Dockerfile: {}", e))
        })?;

        Ok(())
    }

    /// Generate README.md for iac directory.
    fn generate_iac_readme(spec: &ProjectSpec, iac_dir: &Path) -> Result<()> {
        let mut content = String::new();

        content.push_str(&format!("# Infrastructure as Code - {}\n\n", spec.name));

        content.push_str(
            "This directory contains Infrastructure as Code templates for provisioning and deployment.\n\n\
             ## Overview\n\n\
             IaC templates use Terraform/OpenTofu, Ansible, and Docker Compose to deploy the application.\n\n\
             ## Directory Structure\n\n",
        );

        if spec.iac_templates.terraform_enabled {
            content.push_str(
                "### `tofu/` - Terraform/OpenTofu\n\n\
                 Terraform configurations for infrastructure provisioning.\n\n\
                 - `common/` - Shared Terraform modules\n\
                 - `hetzner/` - Hetzner Cloud provider configuration\n\
                 - `lxd/` - LXD/Incus local containers\n\n\
                 **Usage:**\n\n\
                 ```bash\n\
                 cd iac/tofu/hetzner\n\
                 terraform init\n\
                 terraform plan\n\
                 terraform apply\n\
                 ```\n\n",
            );
        }

        if spec.iac_templates.ansible_enabled {
            content.push_str(
                "### `ansible/` - Configuration Management\n\n\
                 Ansible playbooks for application deployment and configuration.\n\n\
                 - `playbook.yml` - Main deployment playbook\n\
                 - `inventory.ini` - Host inventory\n\
                 - `roles/` - Ansible roles\n\n\
                 **Usage:**\n\n\
                 ```bash\n\
                 cd iac/ansible\n\
                 ansible-playbook -i inventory.ini playbook.yml\n\
                 ```\n\n",
            );
        }

        if spec.iac_templates.docker_compose_enabled {
            content.push_str(
                "### `docker/` - Container Orchestration\n\n\
                 Docker Compose configurations for containerized deployment.\n\n\
                 - `docker-compose.yml` - Main services\n\
                 - `docker-compose.monitoring.yml` - Monitoring stack (optional)\n\
                 - `Dockerfile` - Application container image\n\n\
                 **Usage:**\n\n\
                 ```bash\n\
                 cd iac/docker\n\
                 docker-compose up -d\n\
                 docker-compose -f docker-compose.yml -f docker-compose.monitoring.yml up -d  # With monitoring\n\
                 ```\n\n",
            );
        }

        content.push_str(
            "## Integration with Nickel Configuration\n\n\
             All IaC templates reference the Nickel configuration generated by typedialog:\n\n\
             1. **TypeDialog** collects configuration → `config.json`\n\
             2. **json-to-nickel** converts to Nickel → `config.ncl`\n\
             3. **Validators** enforce constraints\n\
             4. **IaC templates** reference validated config\n\n\
             ### Example: Terraform\n\n\
             ```hcl\n\
             # Use Nickel config for variables\n\
             locals {\n\
               config = yamldecode(file(\"../../config.ncl\"))\n\
             }\n\
             \n\
             resource \"server\" \"main\" {\n\
               name = local.config.server.name\n\
               port = local.config.server.port\n\
             }\n\
             ```\n\n\
             ### Example: Ansible\n\n\
             ```yaml\n\
             - name: Deploy configuration\n\
               template:\n\
                 src: config.ncl.j2\n\
                 dest: /etc/app/config.ncl\n\
               vars:\n\
                 config_data: \"{{ lookup('file', '../../config.ncl') }}\"\n\
             ```\n\n\
             ## Prerequisites\n\n",
        );

        if spec.iac_templates.terraform_enabled {
            content.push_str("- **terraform** or **tofu** - Infrastructure provisioning\n");
        }
        if spec.iac_templates.ansible_enabled {
            content.push_str("- **ansible** - Configuration management\n");
        }
        if spec.iac_templates.docker_compose_enabled {
            content.push_str("- **docker** and **docker-compose** - Container runtime\n");
        }
        content.push_str("- **nickel** - Configuration language runtime\n\n");

        content.push_str(
            "## Workflow\n\n\
             1. Generate configuration: `./scripts/configure.sh`\n\
             2. Provision infrastructure: `cd iac/tofu/hetzner && terraform apply`\n\
             3. Deploy application: `cd iac/ansible && ansible-playbook playbook.yml`\n\
             4. Verify deployment: Check monitoring dashboards\n\n",
        );

        let readme_file = iac_dir.join("README.md");
        std::fs::write(&readme_file, content).map_err(|e| {
            crate::error::ProvisioningGenError::Other(format!("Failed to write iac README: {}", e))
        })?;

        Ok(())
    }
}
