// ABOUTME: CLI command for declarative deployment of EdgeConnect applications from YAML configuration
// ABOUTME: Integrates config parser, deployment planner, and resource manager for complete deployment workflow
package cli

import (
	"context"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"strings"
	"time"

	"edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/adapters/driven/edgeconnect"
	"edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/core/apply"
	"edp.buildth.ing/DevFW-CICD/edge-connect-client/internal/config"
	"github.com/spf13/cobra"
)

var (
	configFile  string
	dryRun      bool
	autoApprove bool
)

var applyCmd = &cobra.Command{
	Use:   "apply",
	Short: "Deploy EdgeConnect applications from configuration files",
	Long: `Deploy EdgeConnect applications and their instances from YAML configuration files.
This command reads a configuration file, analyzes the current state, and applies
the necessary changes to deploy your applications across multiple cloudlets.`,
	Run: func(cmd *cobra.Command, args []string) {
		if configFile == "" {
			fmt.Fprintf(os.Stderr, "Error: configuration file is required\n")
			cmd.Usage()
			os.Exit(1)
		}

		if err := runApply(configFile, dryRun, autoApprove); err != nil {
			fmt.Fprintf(os.Stderr, "Error: %v\n", err)
			os.Exit(1)
		}
	},
}

func runApply(configPath string, isDryRun bool, autoApprove bool) error {
	// Step 1: Validate and resolve config file path
	absPath, err := filepath.Abs(configPath)
	if err != nil {
		return fmt.Errorf("failed to resolve config file path: %w", err)
	}

	if _, err := os.Stat(absPath); os.IsNotExist(err) {
		return fmt.Errorf("configuration file not found: %s", absPath)
	}

	fmt.Printf("📄 Loading configuration from: %s\n", absPath)

	// Step 2: Parse and validate configuration
	parser := config.NewParser()
	cfg, manifestContent, err := parser.ParseFile(absPath)
	if err != nil {
		return fmt.Errorf("failed to parse configuration: %w", err)
	}

	if err := parser.Validate(cfg); err != nil {
		return fmt.Errorf("configuration validation failed: %w", err)
	}

	fmt.Printf("✅ Configuration loaded successfully: %s\n", cfg.Metadata.Name)

	// Step 3: Create EdgeConnect client
	baseURL := getEnvOrDefault("EDGEXR_BASE_URL", "https://hub.apps.edge.platform.mg3.mdb.osc.live")
	token := getEnvOrDefault("EDGEXR_TOKEN", "")
	username := getEnvOrDefault("EDGEXR_USERNAME", "")
	password := getEnvOrDefault("EDGEXR_PASSWORD", "")

	var client *edgeconnect.Client

	if token != "" {
		fmt.Println("🔐 Using Bearer token authentication")
		client = edgeconnect.NewClient(baseURL,
			edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
			edgeconnect.WithAuthProvider(edgeconnect.NewStaticTokenProvider(token)),
			edgeconnect.WithLogger(log.Default()),
		)
	} else if username != "" && password != "" {
		fmt.Println("🔐 Using username/password authentication")
		client = edgeconnect.NewClientWithCredentials(baseURL, username, password,
			edgeconnect.WithHTTPClient(&http.Client{Timeout: 30 * time.Second}),
			edgeconnect.WithLogger(log.Default()),
		)
	} else {
		log.Fatal("Authentication required: Set either EDGEXR_TOKEN or both EDGEXR_USERNAME and EDGEXR_PASSWORD")
	}

	// Step 4: Create deployment planner
	planner := apply.NewPlanner(client, client)

	// Step 5: Generate deployment plan
	fmt.Println("🔍 Analyzing current state and generating deployment plan...")

	planOptions := apply.DefaultPlanOptions()
	planOptions.DryRun = isDryRun

	result, err := planner.PlanWithOptions(context.Background(), cfg, planOptions)
	if err != nil {
		return fmt.Errorf("failed to generate deployment plan: %w", err)
	}

	// Step 6: Display plan summary
	fmt.Println("\n📋 Deployment Plan:")
	fmt.Println(strings.Repeat("=", 50))
	fmt.Println(result.Plan.Summary)
	fmt.Println(strings.Repeat("=", 50))

	// Display warnings if any
	if len(result.Warnings) > 0 {
		fmt.Println("\n⚠️  Warnings:")
		for _, warning := range result.Warnings {
			fmt.Printf("   • %s\n", warning)
		}
	}

	// Step 7: If dry-run, stop here
	if isDryRun {
		fmt.Println("\n🔍 Dry-run complete. No changes were made.")
		return nil
	}

	// Step 8: Confirm deployment (in non-dry-run mode)
	if result.Plan.TotalActions == 0 {
		fmt.Println("\n✅ No changes needed. Resources are already in desired state.")
		return nil
	}

	fmt.Printf("\nThis will perform %d actions. Estimated time: %v\n",
		result.Plan.TotalActions, result.Plan.EstimatedDuration)

	if !autoApprove && !confirmDeployment() {
		fmt.Println("Deployment cancelled.")
		return nil
	}

	// Step 9: Execute deployment
	fmt.Println("\n🚀 Starting deployment...")

	manager := apply.NewResourceManager(client, client, apply.WithLogger(log.Default()))
	deployResult, err := manager.ApplyDeployment(context.Background(), result.Plan, cfg, manifestContent)
	if err != nil {
		return fmt.Errorf("deployment failed: %w", err)
	}

	// Step 10: Display results
	if deployResult.Success {
		fmt.Printf("\n✅ Deployment completed successfully in %v\n", deployResult.Duration)
		if len(deployResult.CompletedActions) > 0 {
			fmt.Println("\nCompleted actions:")
			for _, action := range deployResult.CompletedActions {
				fmt.Printf("   ✅ %s %s\n", action.Type, action.Target)
			}
		}
	} else {
		fmt.Printf("\n❌ Deployment failed after %v\n", deployResult.Duration)
		if deployResult.Error != nil {
			fmt.Printf("Error: %v\n", deployResult.Error)
		}
		if len(deployResult.FailedActions) > 0 {
			fmt.Println("\nFailed actions:")
			for _, action := range deployResult.FailedActions {
				fmt.Printf("   ❌ %s %s: %v\n", action.Type, action.Target, action.Error)
			}
		}
		return fmt.Errorf("deployment failed with %d failed actions", len(deployResult.FailedActions))
	}

	return nil
}

func confirmDeployment() bool {
	fmt.Print("Do you want to proceed? (yes/no): ")
	var response string
	fmt.Scanln(&response)

	switch response {
	case "yes", "y", "YES", "Y":
		return true
	default:
		return false
	}
}

func getEnvOrDefault(key, defaultValue string) string {
	if value := os.Getenv(key); value != "" {
		return value
	}
	return defaultValue
}

func init() {
	rootCmd.AddCommand(applyCmd)

	applyCmd.Flags().StringVarP(&configFile, "file", "f", "", "configuration file path (required)")
	applyCmd.Flags().BoolVar(&dryRun, "dry-run", false, "preview changes without applying them")
	applyCmd.Flags().BoolVar(&autoApprove, "auto-approve", false, "automatically approve the deployment plan")

	applyCmd.MarkFlagRequired("file")
}
