package main

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"time"

	"github.com/aws/aws-sdk-go-v2/aws"
	"github.com/aws/aws-sdk-go-v2/config"
	"github.com/aws/aws-sdk-go-v2/service/ec2"
	"github.com/aws/aws-sdk-go-v2/service/ec2/types"
	"github.com/jaredfolkins/terraform-loading-bar/progress"
)

var (
	sourceDir = "terraform-config"
)

// TerraformFunction represents the type of Terraform operation to perform
type TerraformFunction string

const (
	FunctionApply   TerraformFunction = "apply"
	FunctionDestroy TerraformFunction = "destroy"
	FunctionAMI     TerraformFunction = "ami"
	publicDir                         = "/lemc/public"
	destDir                           = "/lemc/private"
	tfVarsFilename                    = "terraform.tfvars"
)

// Config holds all the configuration values needed for the application
type Config struct {
	// Directory Configuration
	SourceDir         string
	PublicDir         string
	DestDir           string
	WorkingDir        string
	AbsoluteSourceDir string
	AbsoluteDestDir   string

	// Terraform Configuration
	TerraformFunction string

	// AWS Configuration
	AWSRegion          string
	AvailabilityZone   string
	AWSAccessKeyID     string
	AWSSecretAccessKey string

	// LEMC Configuration
	LEMCUUID         string
	LEMCScope        string
	LEMCUsername     string
	LEMCUserID       string
	LEMCInstanceType string
	LEMCHTTPBaseURL  string
	LEMCRecipeName   string
	LEMCPageID       string
	LEMCStepID       string

	// AMI Configuration
	AMIName          string
	SourceInstanceID string
	AMIShareAccounts []string

	// Domain Configuration
	RootDomain     string
	HostedZoneID   string
	DomainName     string
	ResourcePrefix string

	// VM Configuration
	ImageName             string
	Port                  string
	BackendTimeoutSeconds string

	// Existing SSL Certificate
	ExistingSSLCertificateARN string

	// Operating System Configuration
	OperatingSystem string
	IsWindows       bool

	// Disk Configuration
	DiskSize string
}

// GetConfig validates and returns the application configuration from environment variables
func GetConfig() (*Config, error) {
	config := &Config{}

	// Set up directory configuration
	config.SourceDir = "terraform-config"
	config.PublicDir = "/lemc/public"
	config.DestDir = "/lemc/private"

	// Get current working directory
	cwd, err := os.Getwd()
	if err != nil {
		return nil, fmt.Errorf("error getting current working directory: %v", err)
	}
	config.WorkingDir = cwd

	// Resolve absolute paths
	if !filepath.IsAbs(config.SourceDir) {
		config.AbsoluteSourceDir = filepath.Join(cwd, config.SourceDir)
	} else {
		config.AbsoluteSourceDir = config.SourceDir
	}

	if !filepath.IsAbs(config.DestDir) {
		config.AbsoluteDestDir = filepath.Join(cwd, config.DestDir)
	} else {
		config.AbsoluteDestDir = config.DestDir
	}

	// Create required directories
	if err := os.MkdirAll(config.AbsoluteDestDir, os.ModePerm); err != nil {
		return nil, fmt.Errorf("error creating destination directory %s: %v", config.AbsoluteDestDir, err)
	}

	if err := os.MkdirAll(config.PublicDir, os.ModePerm); err != nil {
		return nil, fmt.Errorf("error creating public directory %s: %v", config.PublicDir, err)
	}

	// AWS Configuration
	config.AWSAccessKeyID = os.Getenv("AWS_ACCESS_KEY_ID")
	config.AWSSecretAccessKey = os.Getenv("AWS_SECRET_ACCESS_KEY")
	config.AWSRegion = os.Getenv("AWS_REGION")
	config.AvailabilityZone = os.Getenv("AVAILABILITY_ZONE")

	// LEMC Configuration
	config.LEMCUUID = os.Getenv("LEMC_UUID")
	if config.LEMCUUID == "" {
		config.LEMCUUID = "default-uuid"
	}
	config.LEMCScope = os.Getenv("LEMC_SCOPE")
	config.LEMCUsername = os.Getenv("LEMC_USERNAME")
	config.LEMCUserID = os.Getenv("LEMC_USER_ID")
	config.LEMCInstanceType = os.Getenv("INSTANCE_TYPE")
	config.LEMCHTTPBaseURL = os.Getenv("LEMC_HTTP_DOWNLOAD_BASE_URL")
	config.LEMCRecipeName = os.Getenv("LEMC_RECIPE_NAME")
	config.LEMCPageID = os.Getenv("LEMC_PAGE_ID")
	config.LEMCStepID = os.Getenv("LEMC_STEP_ID")

	// AMI Configuration
	config.AMIName = os.Getenv("AMI_NAME")
	config.SourceInstanceID = os.Getenv("SOURCE_INSTANCE_ID")

	// Parse AMI Share Accounts - comma-separated list of AWS account IDs
	amiShareAccountsEnv := os.Getenv("AMI_SHARE_ACCOUNTS")
	if amiShareAccountsEnv != "" {
		accounts := strings.Split(amiShareAccountsEnv, ",")
		for _, account := range accounts {
			account = strings.TrimSpace(account)
			if account != "" {
				config.AMIShareAccounts = append(config.AMIShareAccounts, account)
			}
		}
	}

	// Domain Configuration
	config.RootDomain = os.Getenv("ROOT_DOMAIN")
	config.HostedZoneID = os.Getenv("HOSTED_ZONE_ID")

	// VM Configuration
	config.ImageName = os.Getenv("AMI")
	config.Port = os.Getenv("PORT_FROM")
	config.BackendTimeoutSeconds = os.Getenv("BACKEND_TIMEOUT_SECONDS")
	if config.BackendTimeoutSeconds == "" {
		config.BackendTimeoutSeconds = "7200" // Default to 7200 seconds
	}

	// Existing SSL Certificate
	config.ExistingSSLCertificateARN = os.Getenv("EXISTING_SSL_CERTIFICATE_ARN")

	// Operating System Configuration
	customAMI := os.Getenv("CUSTOM_AMI")
	config.OperatingSystem = os.Getenv("OPERATING_SYSTEM")

	// Disk Configuration
	config.DiskSize = os.Getenv("DISK_SIZE")
	if config.DiskSize == "" {
		config.DiskSize = "60" // Default to 60GB if not specified
	}

	// Priority order: CUSTOM_AMI > OPERATING_SYSTEM > legacy AMI
	if customAMI != "" {
		config.ImageName = customAMI
		lg(fmt.Sprintf("Using custom AMI: %s", customAMI))
	} else if config.OperatingSystem != "" {
		config.ImageName = config.OperatingSystem
		lg(fmt.Sprintf("Using selected operating system AMI: %s", config.OperatingSystem))
	} else {
		// Fallback to legacy AMI environment variable if no OS selection was made
		legacyAMI := os.Getenv("AMI")
		if legacyAMI != "" {
			config.ImageName = legacyAMI
			lg(fmt.Sprintf("No OS selected, using legacy AMI environment variable: %s", legacyAMI))
		} else {
			lg("Warning: No operating system selected and no AMI environment variable set. Terraform will use its default AMI.")
		}
	}

	// Detect Windows platform using AWS API
	config.IsWindows = false // Default to Linux
	if config.ImageName != "" {
		ctx := context.Background()
		isWindows, err := checkAMIIsWindows(ctx, config.ImageName, config.AWSRegion, config.AWSAccessKeyID, config.AWSSecretAccessKey)
		if err != nil {
			lg(fmt.Sprintf("Warning: Failed to detect AMI platform via AWS API: %v", err))
			lg("Falling back to string-based detection...")
			log.Fatal(err)
		}
		config.IsWindows = isWindows
	}

	// Validate required fields
	if config.LEMCHTTPBaseURL == "" {
		return nil, fmt.Errorf("LEMC_HTTP_DOWNLOAD_BASE_URL environment variable is required")
	}

	// Validate LEMCInstanceType only if TERRAFORM_FUNCTION is "apply"
	terraformFunction := os.Getenv("TERRAFORM_FUNCTION")
	if (terraformFunction == string(FunctionApply) || terraformFunction == "") && config.LEMCInstanceType == "" { // Default to apply if not specified
		return nil, fmt.Errorf("INSTANCE_TYPE environment variable is required when TERRAFORM_FUNCTION is 'apply'")
	}

	// Construct resource prefix
	uuidPrefix := config.LEMCUUID
	if len(uuidPrefix) >= 8 {
		uuidPrefix = uuidPrefix[:8]
	}

	scopePrefix := strings.ToLower(config.LEMCScope)
	if scopePrefix == "individual" {
		scopePrefix = "ind"
	} else if scopePrefix == "shared" {
		scopePrefix = "shd"
	}

	config.ResourcePrefix = fmt.Sprintf("lemc-%s-%s-%s-%s",
		uuidPrefix,
		config.LEMCUsername,
		config.LEMCUserID,
		scopePrefix)

	// Construct domain name if possible
	if config.RootDomain != "" && config.ResourcePrefix != "" {
		config.DomainName = fmt.Sprintf("%s.%s", config.ResourcePrefix, config.RootDomain)
	} else {
		config.DomainName = "default.example.com" // Fallback
	}

	// Ensure LEMC_HTTP_DOWNLOAD_BASE_URL ends with a slash
	if !strings.HasSuffix(config.LEMCHTTPBaseURL, "/") {
		config.LEMCHTTPBaseURL += "/"
	}

	return config, nil
}

// Copies a single file from src to dst.
func copyFile(src, dst string) error {
	sourceFileStat, err := os.Stat(src)
	if err != nil {
		return err
	}

	if !sourceFileStat.Mode().IsRegular() {
		return fmt.Errorf("%s is not a regular file", src)
	}

	source, err := os.Open(src)
	if err != nil {
		return err
	}
	defer source.Close()

	destination, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer destination.Close()

	_, err = io.Copy(destination, source)
	return err
}

// checkAMIIsWindows queries AWS API to determine if an AMI is Windows-based
func checkAMIIsWindows(ctx context.Context, amiID, region, accessKeyID, secretAccessKey string) (bool, error) {
	if amiID == "" {
		return false, fmt.Errorf("AMI ID is empty")
	}

	lg(fmt.Sprintf("Checking AMI platform via AWS API: %s", amiID))

	// Load AWS configuration
	cfg, err := config.LoadDefaultConfig(ctx,
		config.WithRegion(region),
	)
	if err != nil {
		return false, fmt.Errorf("unable to load AWS SDK config: %v", err)
	}

	// Override credentials if provided
	if accessKeyID != "" && secretAccessKey != "" {
		cfg.Credentials = aws.CredentialsProviderFunc(func(ctx context.Context) (aws.Credentials, error) {
			return aws.Credentials{
				AccessKeyID:     accessKeyID,
				SecretAccessKey: secretAccessKey,
			}, nil
		})
	}

	// Create EC2 service client
	ec2Client := ec2.NewFromConfig(cfg)

	// Describe the AMI to get platform information
	input := &ec2.DescribeImagesInput{
		ImageIds: []string{amiID},
	}

	result, err := ec2Client.DescribeImages(ctx, input)
	if err != nil {
		return false, fmt.Errorf("error describing AMI %s: %v", amiID, err)
	}

	if len(result.Images) == 0 {
		return false, fmt.Errorf("AMI %s not found", amiID)
	}

	image := result.Images[0]

	lg(fmt.Sprintf("AMI %s detected as %s platform via AWS API", amiID, image.Platform))
	// Check platform - AWS returns "windows" for Windows AMIs
	isWindows := image.Platform == types.PlatformValuesWindows

	if string(image.Platform) == "windows" {
		isWindows = true
	}

	// Additional logging for debugging
	lg(fmt.Sprintf("AMI details - Name: %s, Platform: %s, Architecture: %s",
		aws.ToString(image.Name),
		string(image.Platform),
		string(image.Architecture)))

	return isWindows, nil
}

func lt(s string) {
	fmt.Printf("lemc.html.trunc; <pre>%s</pre><br>\n", s)
}

func lg(s string) {
	fmt.Printf("lemc.html.append; <pre>%s</pre><br>\n", s)
}

// TerraformInit initializes a Terraform working directory
func TerraformInit(workingDir string) error {
	lg("Running Terraform init...")
	initCmd := exec.Command("terraform", "init")
	initCmd.Stdout = os.Stdout
	initCmd.Stderr = os.Stderr
	initCmd.Dir = workingDir
	return initCmd.Run()
}

// TerraformPlan creates a Terraform execution plan
func TerraformPlan(workingDir string) error {
	lg("Running Terraform plan...")
	planCmd := exec.Command("terraform", "plan", "-out", "terraform.plan")
	planCmd.Stdout = os.Stdout
	planCmd.Stderr = os.Stderr
	planCmd.Dir = workingDir
	return planCmd.Run()
}

// runTerraformCommandWithProgress executes a Terraform command and handles its progress output
func runTerraformCommandWithProgress(cmd *exec.Cmd, operation string) error {
	output, err := cmd.StdoutPipe()
	if err != nil {
		return fmt.Errorf("error creating stdout pipe: %v", err)
	}
	cmd.Stderr = os.Stderr

	if err := cmd.Start(); err != nil {
		return fmt.Errorf("error starting %s command: %v", operation, err)
	}

	progressHandler := progress.NewProgressHandler(output)

	for {
		line, err := progressHandler.ReadLine()
		if err != nil {
			if err == io.EOF {
				break
			}
			lg(fmt.Sprintf("Error: Terraform %s failed. %v", operation, err))
			for _, line := range progressHandler.GetOriginalOutput() {
				lg(line)
			}
			return fmt.Errorf("error reading %s output: %v", operation, err)
		}

		if line != "" {
			formattedLine := strings.TrimSpace(line)
			lt(formattedLine)
		}
	}

	if err := cmd.Wait(); err != nil {
		lg(fmt.Sprintf("Error: Terraform %s failed. %v", operation, err))
		for _, line := range progressHandler.GetOriginalOutput() {
			lg(line)
		}
		return fmt.Errorf("error waiting for %s command: %v", operation, err)
	}

	lg(fmt.Sprintf("Terraform %s completed.", operation))
	lg(fmt.Sprintf("Complete Terraform %s JSON output:", operation))

	for _, line := range progressHandler.GetOriginalOutput() {
		log.Println(line)
	}

	return nil
}

// TerraformApply applies the Terraform plan
func TerraformApply(workingDir string) error {
	lg("Running Terraform apply...")
	applyCmd := exec.Command("terraform", "apply", "-auto-approve", "-json", "terraform.plan")
	applyCmd.Dir = workingDir
	return runTerraformCommandWithProgress(applyCmd, "apply")
}

// TerraformDestroy destroys the Terraform-managed infrastructure
func TerraformDestroy(workingDir string) error {
	lg("Running Terraform destroy...")
	destroyCmd := exec.Command("terraform", "destroy", "-auto-approve", "-json")
	destroyCmd.Dir = workingDir
	return runTerraformCommandWithProgress(destroyCmd, "destroy")
}

// GetTerraformOutputs retrieves the Terraform outputs as a map
func GetTerraformOutputs(workingDir string) (map[string]struct {
	Value     interface{} `json:"value"`
	Type      string      `json:"type"`
	Sensitive bool        `json:"sensitive"`
}, error) {
	lg("Retrieving Terraform outputs...")
	outputCmd := exec.Command("terraform", "output", "-json")
	outputCmd.Dir = workingDir
	outputJSON, err := outputCmd.Output()
	if err != nil {
		return nil, fmt.Errorf("error running terraform output -json: %v", err)
	}

	var outputs map[string]struct {
		Value     interface{} `json:"value"`
		Type      string      `json:"type"`
		Sensitive bool        `json:"sensitive"`
	}
	if err := json.Unmarshal(outputJSON, &outputs); err != nil {
		return nil, fmt.Errorf("error parsing terraform output JSON: %v", err)
	}

	return outputs, nil
}

// GenerateTerraformVars generates the content for terraform.tfvars
func (c *Config) GenerateTerraformVars() string {
	var content strings.Builder

	// Map of environment variables to Terraform variables
	varMap := map[string]string{
		"AWS_REGION":        "aws_region",
		"AVAILABILITY_ZONE": "availability_zone",
		"LEMC_SCOPE":        "lemc_scope",
		"LEMC_USERNAME":     "lemc_username",
		"LEMC_USER_ID":      "lemc_user_id",
		"PORT_FROM":         "port",
	}

	// Add mapped variables
	for envKey, tfKey := range varMap {
		var value string
		// Prioritize project_id from key file
		if envKey == "AWS_REGION" && c.AWSRegion != "" {
			value = c.AWSRegion
			lg(fmt.Sprintf("Using AWS_REGION from key file for tfvar %s: %s", tfKey, value))
		} else {
			value = os.Getenv(envKey)
		}

		if value != "" {
			content.WriteString(fmt.Sprintf("%s = \"%s\"\n", tfKey, value))
		} else {
			// Log warning only if not AWS_REGION (which might be intentionally overridden by key)
			// or if it's AWS_REGION but was not found in key either.
			if envKey != "AWS_REGION" || (envKey == "AWS_REGION" && c.AWSRegion == "") {
				lg(fmt.Sprintf("Warning: Environment variable %s not found, and not overridden by key file.", envKey))
			}
		}
	}

	// Add secondary availability zone derived from primary AZ
	primaryAZ := os.Getenv("AVAILABILITY_ZONE")
	if primaryAZ != "" {
		// Derive secondary AZ by changing the last character (e.g., us-west-2a -> us-west-2b)
		secondaryAZ := primaryAZ[:len(primaryAZ)-1] + "b"
		content.WriteString(fmt.Sprintf("availability_zone_secondary = \"%s\"\n", secondaryAZ))
		lg(fmt.Sprintf("Derived secondary availability zone: %s from primary: %s", secondaryAZ, primaryAZ))
	} else {
		lg("Warning: AVAILABILITY_ZONE not found in environment, cannot derive secondary AZ.")
	}

	// Add lemc_uuid
	content.WriteString(fmt.Sprintf("lemc_uuid = \"%s\"\n", c.LEMCUUID))

	// Add resource prefix
	content.WriteString(fmt.Sprintf("resource_prefix = \"%s\"\n", c.ResourcePrefix))

	// Add machine type if specified
	if c.LEMCInstanceType != "" {
		content.WriteString(fmt.Sprintf("instance_type = \"%s\"\n", c.LEMCInstanceType))
	} else {
		lg("Warning: INSTANCE_TYPE not found in environment. Terraform will use its default.")
	}

	// Add image if specified
	if c.ImageName != "" {
		content.WriteString(fmt.Sprintf("image = \"%s\"\n", c.ImageName))
		lg(fmt.Sprintf("Using AMI: %s", c.ImageName))
	} else {
		lg("Warning: AMI not found in environment. Terraform will use its default.")
	}

	// Add domain name
	content.WriteString(fmt.Sprintf("domain_name = \"%s\"\n", c.DomainName))

	// Add DNS zone name
	if c.HostedZoneID != "" {
		content.WriteString(fmt.Sprintf("hosted_zone_id = \"%s\"\n", c.HostedZoneID))
	} else {
		lg("Warning: HOSTED_ZONE_ID not found in environment, required for hosted_zone_id.")
		content.WriteString(fmt.Sprintf("hosted_zone_id = \"%s\"\n", "default-zone-name")) // Placeholder
	}

	// Add backend timeout
	content.WriteString(fmt.Sprintf("backend_timeout_seconds = %s\n", c.BackendTimeoutSeconds))

	// Add existing SSL certificate ARN if provided
	if c.ExistingSSLCertificateARN != "" {
		content.WriteString(fmt.Sprintf("existing_ssl_certificate_arn = \"%s\"\n", c.ExistingSSLCertificateARN))
	} else {
		// If not provided, Terraform will expect it to be an empty string or not set, which is fine as it's optional there.
		// We could also choose to not write the variable at all if empty.
		lg("Warning: EXISTING_SSL_CERTIFICATE_ARN not provided. If you intend to use an existing certificate, ensure this variable is set in Terraform or the environment.")
	}

	// Add Windows flag
	content.WriteString(fmt.Sprintf("is_windows = %t\n", c.IsWindows))
	if c.IsWindows {
		lg("Configuring for Windows instance (RDP access)")
	} else {
		lg("Configuring for Linux instance (SSH access)")
	}

	// Add nested virtualization support (enabled by default)
	content.WriteString(fmt.Sprintf("enable_nested_virtualization = %t\n", true))
	lg("Nested virtualization support enabled for Docker, WSL, VMs, etc.")

	// Add disk size
	content.WriteString(fmt.Sprintf("disk_size = %s\n", c.DiskSize))
	lg(fmt.Sprintf("Using disk size: %s GB", c.DiskSize))

	return content.String()
}

// runApply executes the Terraform apply operation and handles all related tasks
func runApply(config *Config) error {
	// Initialize Terraform
	if err := TerraformInit(config.AbsoluteDestDir); err != nil {
		return fmt.Errorf("error initializing Terraform: %v", err)
	}

	// Create and apply Terraform plan
	if err := TerraformPlan(config.AbsoluteDestDir); err != nil {
		return fmt.Errorf("error creating Terraform plan: %v", err)
	}

	if err := TerraformApply(config.AbsoluteDestDir); err != nil {
		return fmt.Errorf("error applying Terraform plan: %v", err)
	}

	// Get Terraform outputs
	outputs, err := GetTerraformOutputs(config.AbsoluteDestDir)
	if err != nil {
		return fmt.Errorf("error getting Terraform outputs: %v", err)
	}

	publicIPOutput, ipOk := outputs["public_ip"]
	if !ipOk || publicIPOutput.Value == nil {
		return fmt.Errorf("public_ip not found in Terraform outputs")
	}

	publicIP, castOk := publicIPOutput.Value.(string)
	if !castOk || publicIP == "" {
		return fmt.Errorf("could not retrieve or cast public_ip from Terraform outputs to string")
	}

	if config.IsWindows {
		// Handle Windows authentication with RDP
		return handleWindowsAuthentication(config, outputs, publicIP)
	} else {
		// Handle Linux authentication with SSH
		return handleLinuxAuthentication(config, outputs, publicIP)
	}
}

// handleLinuxAuthentication handles SSH key generation and instructions for Linux instances
func handleLinuxAuthentication(config *Config, outputs map[string]struct {
	Value     interface{} `json:"value"`
	Type      string      `json:"type"`
	Sensitive bool        `json:"sensitive"`
}, publicIP string) error {
	privateKeyOutput, ok := outputs["private_ssh_key"]
	if !ok || privateKeyOutput.Value == nil {
		return fmt.Errorf("private_ssh_key not found in Terraform outputs")
	}

	privateKey, ok := privateKeyOutput.Value.(string)
	if !ok {
		return fmt.Errorf("private_ssh_key in Terraform outputs is not a string")
	}
	sshKeyFilename := "private-ssh-key"
	sshKeyPath := filepath.Join(config.PublicDir, sshKeyFilename)

	lg(fmt.Sprintf("Saving private SSH key to %s...", sshKeyPath))

	if err := os.WriteFile(sshKeyPath, []byte(privateKey), 0600); err != nil {
		return fmt.Errorf("error writing private SSH key to file: %v", err)
	}

	lg(fmt.Sprintf("Private SSH key saved to %s", sshKeyPath))

	downloadURL := config.LEMCHTTPBaseURL + sshKeyFilename
	lg(fmt.Sprintf(`SSH Private Key available for download: <a href="%s" target="_blank" style="color: blue; text-decoration: none;">%s</a>`, downloadURL, sshKeyFilename))
	lg(fmt.Sprintf("IMPORTANT: Secure this key. It provides access to the created EC2 instance. Download it and then ensure it is removed from the public folder if this is a shared environment or if the link is accessible by others."))

	// Use 'admin' as the default user for Debian AMIs instead of LEMC_USERNAME
	sshCommand := fmt.Sprintf("ssh -i %s admin@%s", sshKeyFilename, publicIP)
	lg(fmt.Sprintf("To SSH into the EC2 instance (once key is downloaded and in current directory): <code>%s</code>", sshCommand))

	// Create .env file
	lg("Creating .env file...")

	s := `
SSH_USERNAME=admin
VM_IP=%s
DOMAIN_NAME=https://%s
`

	envContent := fmt.Sprintf(s, publicIP, config.DomainName)
	envFilePath := filepath.Join(config.PublicDir, "dotenv")

	if err := os.WriteFile(envFilePath, []byte(envContent), 0644); err != nil {
		lg(fmt.Sprintf("Error writing dotenv file: %v", err))
	} else {
		lg(fmt.Sprintf(".env file saved to %s", envFilePath))
		envFileDownloadURL := config.LEMCHTTPBaseURL + "dotenv"
		lg(fmt.Sprintf(`.env file available for download: <a href="%s" target="_blank" style="color: blue; text-decoration: none;">dotenv</a>`, envFileDownloadURL))
	}

	return nil
}

// handleWindowsAuthentication handles RDP file generation and instructions for Windows instances
func handleWindowsAuthentication(config *Config, outputs map[string]struct {
	Value     interface{} `json:"value"`
	Type      string      `json:"type"`
	Sensitive bool        `json:"sensitive"`
}, publicIP string) error {
	windowsPasswordOutput, ok := outputs["windows_password"]
	if !ok || windowsPasswordOutput.Value == nil {
		return fmt.Errorf("windows_password not found in Terraform outputs")
	}

	windowsPassword, ok := windowsPasswordOutput.Value.(string)
	if !ok {
		return fmt.Errorf("windows_password in Terraform outputs is not a string")
	}

	if windowsPassword == "" {
		lg("Warning: Windows password is empty. The instance may still be initializing. Please wait a few minutes and try recreating the instance.")
		return fmt.Errorf("Windows password is not yet available")
	}

	// Create RDP file
	rdpFilename := "windows-connection.rdp"
	rdpPath := filepath.Join(config.PublicDir, rdpFilename)

	rdpContent := fmt.Sprintf(`full address:s:%s
username:s:Administrator
`, publicIP)

	lg(fmt.Sprintf("Creating RDP file at %s...", rdpPath))

	if err := os.WriteFile(rdpPath, []byte(rdpContent), 0644); err != nil {
		return fmt.Errorf("error writing RDP file: %v", err)
	}

	lg(fmt.Sprintf("RDP file created at %s", rdpPath))

	// Create credentials file
	credsFilename := "windows-credentials.txt"
	credsPath := filepath.Join(config.PublicDir, credsFilename)

	credsContent := fmt.Sprintf(`Windows Server Connection Details
================================

Server IP: %s
Username: Administrator
Password: %s

Instructions:
1. Download the RDP file
2. Open the RDP file with your Remote Desktop client
3. Use the username and password above when prompted

`, publicIP, windowsPassword)

	if err := os.WriteFile(credsPath, []byte(credsContent), 0600); err != nil {
		return fmt.Errorf("error writing credentials file: %v", err)
	}

	// Provide download links
	rdpDownloadURL := config.LEMCHTTPBaseURL + rdpFilename
	credsDownloadURL := config.LEMCHTTPBaseURL + credsFilename

	lg(fmt.Sprintf(`RDP Connection File: <a href="%s" target="_blank" style="color: blue; text-decoration: none;">%s</a>`, rdpDownloadURL, rdpFilename))
	lg(fmt.Sprintf(`Windows Credentials: <a href="%s" target="_blank" style="color: blue; text-decoration: none;">%s</a>`, credsDownloadURL, credsFilename))

	lg(fmt.Sprintf("Windows Server Details:<br>"))
	lg(fmt.Sprintf("IP Address: <code>%s</code><br>", publicIP))
	lg(fmt.Sprintf("Username: <code>Administrator</code><br>"))
	lg(fmt.Sprintf("Password: <code>%s</code><br>", windowsPassword))
	lg(fmt.Sprintf("IMPORTANT: Secure these credentials. They provide full administrative access to the Windows server."))

	// Create .env file
	lg("Creating .env file...")

	envContent := fmt.Sprintf(`
RDP_SERVER=%s
RDP_USERNAME=Administrator
RDP_PASSWORD=%s
DOMAIN_NAME=https://%s
`, publicIP, windowsPassword, config.DomainName)

	envFilePath := filepath.Join(config.PublicDir, "dotenv")

	if err := os.WriteFile(envFilePath, []byte(envContent), 0644); err != nil {
		lg(fmt.Sprintf("Error writing dotenv file: %v", err))
	} else {
		lg(fmt.Sprintf(".env file saved to %s", envFilePath))
		envFileDownloadURL := config.LEMCHTTPBaseURL + "dotenv"
		lg(fmt.Sprintf(`.env file available for download: <a href="%s" target="_blank" style="color: blue; text-decoration: none;">dotenv</a>`, envFileDownloadURL))
	}

	return nil
}

// runDestroy executes the Terraform destroy operation
func runDestroy(config *Config) error {
	// Initialize Terraform
	if err := TerraformInit(config.AbsoluteDestDir); err != nil {
		return fmt.Errorf("error initializing Terraform: %v", err)
	}

	if err := TerraformDestroy(config.AbsoluteDestDir); err != nil {
		return fmt.Errorf("error destroying Terraform resources: %v", err)
	}
	lg("Terraform destroy completed successfully.")
	return nil
}

// findInstanceByLEMCTags searches for EC2 instances using LEMC resource tags
func findInstanceByLEMCTags(config *Config) (string, error) {
	lg("Searching for instances with LEMC tags...")
	lg(fmt.Sprintf("Looking for instances with resource prefix: %s", config.ResourcePrefix))

	// Create AWS CLI command to find instances with matching tags
	describeCmd := exec.Command("aws", "ec2", "describe-instances",
		"--region", config.AWSRegion,
		"--filters",
		fmt.Sprintf("Name=tag:Name,Values=%s", config.ResourcePrefix),
		fmt.Sprintf("Name=tag:LEMC_UUID,Values=%s", config.LEMCUUID),
		fmt.Sprintf("Name=tag:LEMC_Username,Values=%s", config.LEMCUsername),
		fmt.Sprintf("Name=instance-state-name,Values=running,stopped"), // Only running or stopped instances
		"--query", "Reservations[*].Instances[*].[InstanceId,State.Name,Tags[?Key=='Name'].Value|[0]]",
		"--output", "json")

	// Set AWS credentials as environment variables for the command
	describeCmd.Env = append(os.Environ(),
		fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", config.AWSAccessKeyID),
		fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", config.AWSSecretAccessKey),
		fmt.Sprintf("AWS_DEFAULT_REGION=%s", config.AWSRegion))

	output, err := describeCmd.Output()
	if err != nil {
		return "", fmt.Errorf("error querying AWS for instances: %v", err)
	}

	// Parse the JSON output
	var reservations [][][]interface{}
	if err := json.Unmarshal(output, &reservations); err != nil {
		return "", fmt.Errorf("error parsing instance query results: %v", err)
	}

	// Find matching instances
	var matchingInstances []string
	for _, reservation := range reservations {
		for _, instance := range reservation {
			if len(instance) >= 3 {
				instanceID, ok1 := instance[0].(string)
				state, ok2 := instance[1].(string)
				name, ok3 := instance[2].(string)

				if ok1 && ok2 && ok3 {
					lg(fmt.Sprintf("Found instance: %s (state: %s, name: %s)", instanceID, state, name))
					if state == "running" || state == "stopped" {
						matchingInstances = append(matchingInstances, instanceID)
					}
				}
			}
		}
	}

	if len(matchingInstances) == 0 {
		return "", fmt.Errorf("no running or stopped instances found with LEMC tags (resource_prefix=%s, lemc_uuid=%s, lemc_user=%s)",
			config.ResourcePrefix, config.LEMCUUID, config.LEMCUsername)
	}

	if len(matchingInstances) > 1 {
		lg(fmt.Sprintf("Warning: Found %d matching instances. Using the first one: %s", len(matchingInstances), matchingInstances[0]))
		lg("If you want to use a specific instance, set SOURCE_INSTANCE_ID environment variable.")
		for i, instanceID := range matchingInstances {
			lg(fmt.Sprintf("  %d: %s", i+1, instanceID))
		}
	}

	return matchingInstances[0], nil
}

// shareAMIWithAccounts shares the AMI with the specified AWS account IDs
func shareAMIWithAccounts(config *Config, amiID string) error {
	lg(fmt.Sprintf("AMI sharing configuration: AMI_SHARE_ACCOUNTS environment variable contains %d account(s)", len(config.AMIShareAccounts)))

	if len(config.AMIShareAccounts) == 0 {
		lg("No accounts specified for AMI sharing. Skipping account sharing.")
		return nil
	}

	lg(fmt.Sprintf("Sharing AMI %s with %d account(s)...", amiID, len(config.AMIShareAccounts)))
	for i, account := range config.AMIShareAccounts {
		lg(fmt.Sprintf("  %d: %s", i+1, account))
	}

	for _, accountID := range config.AMIShareAccounts {
		lg(fmt.Sprintf("Sharing AMI with account: %s", accountID))

		shareCmd := exec.Command("aws", "ec2", "modify-image-attribute",
			"--image-id", amiID,
			"--launch-permission", fmt.Sprintf("Add=[{UserId=%s}]", accountID),
			"--region", config.AWSRegion)

		// Set AWS credentials as environment variables for the command
		shareCmd.Env = append(os.Environ(),
			fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", config.AWSAccessKeyID),
			fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", config.AWSSecretAccessKey),
			fmt.Sprintf("AWS_DEFAULT_REGION=%s", config.AWSRegion))

		output, err := shareCmd.CombinedOutput()
		if err != nil {
			lg(fmt.Sprintf("Warning: Failed to share AMI with account %s: %v", accountID, err))
			lg(fmt.Sprintf("AWS CLI output: %s", string(output)))
			lg("This may be due to invalid account ID or insufficient permissions.")
		} else {
			lg(fmt.Sprintf("Successfully shared AMI with account: %s", accountID))
			if len(output) > 0 {
				lg(fmt.Sprintf("AWS CLI output: %s", string(output)))
			}
		}
	}

	lg("AMI sharing process completed.")
	return nil
}

// waitForAMIAvailable waits for an AMI to become available
func waitForAMIAvailable(config *Config, amiID string) error {
	lg(fmt.Sprintf("Waiting for AMI %s to become available...", amiID))

	maxAttempts := 60 // Maximum 60 attempts (30 minutes with 30-second intervals)
	attempt := 0

	for attempt < maxAttempts {
		attempt++
		lg(fmt.Sprintf("Checking AMI status (attempt %d/%d)...", attempt, maxAttempts))

		// Check AMI status using AWS CLI
		statusCmd := exec.Command("aws", "ec2", "describe-images",
			"--image-ids", amiID,
			"--region", config.AWSRegion,
			"--query", "Images[0].State",
			"--output", "text")

		// Set AWS credentials as environment variables for the command
		statusCmd.Env = append(os.Environ(),
			fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", config.AWSAccessKeyID),
			fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", config.AWSSecretAccessKey),
			fmt.Sprintf("AWS_DEFAULT_REGION=%s", config.AWSRegion))

		output, err := statusCmd.Output()
		if err != nil {
			lg(fmt.Sprintf("Error checking AMI status: %v", err))
			if attempt >= maxAttempts {
				return fmt.Errorf("failed to check AMI status after %d attempts: %v", maxAttempts, err)
			}
			lg("Retrying in 30 seconds...")
			time.Sleep(30 * time.Second)
			continue
		}

		status := strings.TrimSpace(string(output))
		lg(fmt.Sprintf("AMI status: %s", status))

		switch status {
		case "available":
			lg(fmt.Sprintf("AMI %s is now available and ready for use!", amiID))
			return nil
		case "pending":
			lg("AMI is still pending, waiting 30 seconds before checking again...")
			time.Sleep(30 * time.Second)
		case "failed":
			return fmt.Errorf("AMI creation failed")
		default:
			lg(fmt.Sprintf("Unknown AMI status: %s, waiting 30 seconds...", status))
			time.Sleep(30 * time.Second)
		}
	}

	return fmt.Errorf("AMI did not become available within the timeout period (%d minutes)", maxAttempts/2)
}

// runAMI executes AMI creation from the current instance
func runAMI(config *Config) error {
	lg("Starting AMI creation process...")

	// Validate AMI name is provided
	if config.AMIName == "" {
		return fmt.Errorf("AMI_NAME environment variable is required for AMI creation")
	}

	// Validate AWS credentials are available
	if config.AWSAccessKeyID == "" || config.AWSSecretAccessKey == "" {
		return fmt.Errorf("AWS credentials (AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY) are required for AMI creation")
	}

	// Validate AWS region is provided
	if config.AWSRegion == "" {
		return fmt.Errorf("AWS_REGION environment variable is required for AMI creation")
	}

	var instanceID string

	// If SOURCE_INSTANCE_ID is provided, use it directly
	if config.SourceInstanceID != "" {
		instanceID = config.SourceInstanceID
		lg(fmt.Sprintf("Using provided source instance ID: %s", instanceID))
	} else {
		// Try to get current instance ID from metadata service (if running on EC2)
		lg("SOURCE_INSTANCE_ID not provided. Attempting to detect current instance...")
		metadataCmd := exec.Command("curl", "-s", "--connect-timeout", "3", "http://169.254.169.254/latest/meta-data/instance-id")
		instanceIDBytes, err := metadataCmd.Output()
		if err != nil {
			lg("Could not detect current instance from metadata service.")
			lg("Attempting to find instance using LEMC resource tags...")

			// Try to find instance using AWS API with LEMC tags
			foundInstanceID, err := findInstanceByLEMCTags(config)
			if err != nil {
				lg("Could not find instance using LEMC tags.")
				lg("Please provide SOURCE_INSTANCE_ID environment variable with the instance ID you want to create an AMI from.")
				lg("Example: SOURCE_INSTANCE_ID=i-1234567890abcdef0")
				return fmt.Errorf("SOURCE_INSTANCE_ID is required when not running on EC2 instance and no matching instance found: %v", err)
			}
			instanceID = foundInstanceID
			lg(fmt.Sprintf("Found instance using LEMC tags: %s", instanceID))
		} else {
			instanceID = strings.TrimSpace(string(instanceIDBytes))
			if instanceID == "" || len(instanceID) < 10 {
				lg("Error: Retrieved instance ID appears to be invalid or empty.")
				lg("Please provide SOURCE_INSTANCE_ID environment variable.")
				return fmt.Errorf("invalid instance ID detected: %s", instanceID)
			}
			lg(fmt.Sprintf("Detected current instance ID: %s", instanceID))
		}
	}

	// Validate instance ID format
	if !strings.HasPrefix(instanceID, "i-") || len(instanceID) < 10 {
		return fmt.Errorf("invalid instance ID format: %s (should be like i-1234567890abcdef0)", instanceID)
	}

	// Verify instance exists using AWS API
	lg(fmt.Sprintf("Verifying instance %s exists and is accessible...", instanceID))
	describeCmd := exec.Command("aws", "ec2", "describe-instances",
		"--instance-ids", instanceID,
		"--region", config.AWSRegion,
		"--query", "Reservations[0].Instances[0].State.Name",
		"--output", "text")

	// Set AWS credentials as environment variables for the command
	describeCmd.Env = append(os.Environ(),
		fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", config.AWSAccessKeyID),
		fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", config.AWSSecretAccessKey),
		fmt.Sprintf("AWS_DEFAULT_REGION=%s", config.AWSRegion))

	stateOutput, err := describeCmd.Output()
	if err != nil {
		lg("Error: Unable to access instance information.")
		lg("Please verify:")
		lg("1. Instance ID is correct")
		lg("2. AWS credentials have EC2 permissions")
		lg("3. Instance exists in the specified region")
		return fmt.Errorf("error verifying instance %s: %v", instanceID, err)
	}

	instanceState := strings.TrimSpace(string(stateOutput))
	lg(fmt.Sprintf("Instance %s state: %s", instanceID, instanceState))

	if instanceState == "terminated" {
		return fmt.Errorf("cannot create AMI from terminated instance %s", instanceID)
	}

	// Prepare AMI tags
	tags := map[string]string{
		"Name":                        config.AMIName,
		"LEMC_UUID":                   config.LEMCUUID,
		"LEMC_SCOPE":                  config.LEMCScope,
		"LEMC_USERNAME":               config.LEMCUsername,
		"LEMC_USER_ID":                config.LEMCUserID,
		"LEMC_RECIPE_NAME":            config.LEMCRecipeName,
		"LEMC_PAGE_ID":                config.LEMCPageID,
		"LEMC_STEP_ID":                config.LEMCStepID,
		"LEMC_HTTP_DOWNLOAD_BASE_URL": config.LEMCHTTPBaseURL,
		"CreatedBy":                   "LEMC",
		"ResourcePrefix":              config.ResourcePrefix,
		"SourceInstanceId":            instanceID,
	}

	// Build tag specifications for AWS CLI
	var tagSpecs []string
	for key, value := range tags {
		tagSpecs = append(tagSpecs, fmt.Sprintf("Key=%s,Value=%s", key, value))
	}

	// Create AMI
	lg(fmt.Sprintf("Creating AMI '%s' from instance %s...", config.AMIName, instanceID))

	createCmd := exec.Command("aws", "ec2", "create-image",
		"--instance-id", instanceID,
		"--name", config.AMIName,
		"--description", fmt.Sprintf("AMI created by LEMC for %s from instance %s", config.LEMCUsername, instanceID),
		"--no-reboot",
		"--region", config.AWSRegion)

	// Set AWS credentials as environment variables for the command
	createCmd.Env = append(os.Environ(),
		fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", config.AWSAccessKeyID),
		fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", config.AWSSecretAccessKey),
		fmt.Sprintf("AWS_DEFAULT_REGION=%s", config.AWSRegion))

	createOutput, err := createCmd.Output()
	if err != nil {
		return fmt.Errorf("error creating AMI: %v", err)
	}

	// Parse AMI ID from output
	var result map[string]interface{}
	if err := json.Unmarshal(createOutput, &result); err != nil {
		return fmt.Errorf("error parsing AMI creation output: %v", err)
	}

	amiID, ok := result["ImageId"].(string)
	if !ok {
		return fmt.Errorf("AMI ID not found in creation output")
	}

	lg(fmt.Sprintf("AMI creation initiated. AMI ID: %s", amiID))

	// Wait for AMI to become available before proceeding with tagging and sharing
	if err := waitForAMIAvailable(config, amiID); err != nil {
		return fmt.Errorf("error waiting for AMI to become available: %v", err)
	}

	// Tag the AMI
	lg("Applying tags to the AMI...")
	tagCmd := exec.Command("aws", "ec2", "create-tags",
		"--resources", amiID,
		"--region", config.AWSRegion,
		"--tags")

	// Add all tag specifications
	for _, tagSpec := range tagSpecs {
		tagCmd.Args = append(tagCmd.Args, tagSpec)
	}

	tagCmd.Env = append(os.Environ(),
		fmt.Sprintf("AWS_ACCESS_KEY_ID=%s", config.AWSAccessKeyID),
		fmt.Sprintf("AWS_SECRET_ACCESS_KEY=%s", config.AWSSecretAccessKey),
		fmt.Sprintf("AWS_DEFAULT_REGION=%s", config.AWSRegion))

	if err := tagCmd.Run(); err != nil {
		lg(fmt.Sprintf("Warning: Error applying tags to AMI: %v", err))
	} else {
		lg("Successfully applied tags to AMI")
	}

	// Save AMI information to public directory
	amiInfo := fmt.Sprintf(`AMI Creation Summary
====================
AMI Name: %s
AMI ID: %s
Source Instance ID: %s
Instance State: %s
Region: %s
Created By: %s (%s)
UUID: %s
Scope: %s
Recipe: %s
Resource Prefix: %s

Tags Applied:
`, config.AMIName, amiID, instanceID, instanceState, config.AWSRegion, config.LEMCUsername, config.LEMCUserID, config.LEMCUUID, config.LEMCScope, config.LEMCRecipeName, config.ResourcePrefix)

	for key, value := range tags {
		amiInfo += fmt.Sprintf("  %s: %s\n", key, value)
	}

	// Add shared accounts information
	if len(config.AMIShareAccounts) > 0 {
		amiInfo += fmt.Sprintf("\nShared with AWS Accounts:\n")
		for _, accountID := range config.AMIShareAccounts {
			amiInfo += fmt.Sprintf("  %s\n", accountID)
		}
	} else {
		amiInfo += fmt.Sprintf("\nShared with AWS Accounts: None (AMI is private)\n")
	}

	amiInfoPath := filepath.Join(config.PublicDir, "ami-info.txt")
	if err := os.WriteFile(amiInfoPath, []byte(amiInfo), 0644); err != nil {
		lg(fmt.Sprintf("Warning: Error writing AMI info file: %v", err))
	} else {
		lg(fmt.Sprintf("AMI information saved to %s", amiInfoPath))
		downloadURL := config.LEMCHTTPBaseURL + "ami-info.txt"
		lg(fmt.Sprintf(`AMI Info available for download: <a href="%s" target="_blank" style="color: blue; text-decoration: none;">ami-info.txt</a>`, downloadURL))
	}

	// Share AMI with specified accounts
	if err := shareAMIWithAccounts(config, amiID); err != nil {
		lg(fmt.Sprintf("Warning: Error sharing AMI with accounts: %v", err))
	}

	lg(fmt.Sprintf("AMI creation completed successfully. AMI ID: %s", amiID))
	lg("Note: AMI creation is an asynchronous process. It may take several minutes to complete.")
	lg(fmt.Sprintf("You can monitor progress with: aws ec2 describe-images --image-ids %s --region %s", amiID, config.AWSRegion))

	return nil
}

// RunTerraformFunction executes the specified Terraform function
func RunTerraformFunction(config *Config, function TerraformFunction) error {
	switch function {
	case FunctionApply:
		return runApply(config)
	case FunctionDestroy:
		return runDestroy(config)
	case FunctionAMI:
		return runAMI(config)
	default:
		return fmt.Errorf("unsupported Terraform function: %s", function)
	}
}

func main() {
	lt("Starting Terraform setup...")

	// Get configuration
	config, err := GetConfig()
	if err != nil {
		log.Fatalf("Error getting configuration: %v", err)
	}

	lg(fmt.Sprintf("Running in directory: %s", config.WorkingDir))
	lg("Starting Terraform setup...")

	// Copy files from source directory
	lg(fmt.Sprintf("Copying files from %s to %s...", config.AbsoluteSourceDir, config.AbsoluteDestDir))
	filesToCopy := []string{"main.tf", "outputs.tf", "variables.tf", "configure.sh"}
	for _, filename := range filesToCopy {
		srcPath := filepath.Join(config.AbsoluteSourceDir, filename)
		dstPath := filepath.Join(config.AbsoluteDestDir, filename)
		lg(fmt.Sprintf("  Copying %s to %s", srcPath, dstPath))
		if err := copyFile(srcPath, dstPath); err != nil {
			log.Fatalf("Error copying file %s: %v", filename, err)
		}
	}
	lg("Finished copying base Terraform files.")

	// Generate terraform.tfvars content
	lg("Generating terraform.tfvars content...")
	tfVarsContent := config.GenerateTerraformVars()

	// Write terraform.tfvars file
	tfVarsPath := filepath.Join(config.AbsoluteDestDir, tfVarsFilename)
	lg(fmt.Sprintf("Writing %s...", tfVarsPath))
	err = os.WriteFile(tfVarsPath, []byte(tfVarsContent), 0644)
	if err != nil {
		log.Fatalf("Error writing %s: %v", tfVarsPath, err)
	}

	lg(fmt.Sprintf("Successfully created %s", tfVarsPath))

	// Get Terraform function from environment
	tfFunction := TerraformFunction(os.Getenv("TERRAFORM_FUNCTION"))
	if tfFunction == "" {
		tfFunction = FunctionApply // Default to apply if not specified
	}

	// Run the specified Terraform function
	if err := RunTerraformFunction(config, tfFunction); err != nil {
		log.Fatalf("Error running Terraform function: %v", err)
	}
}
