package cmd

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"io/fs"
	"log"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
)

var processes []*exec.Cmd
var mu sync.Mutex

// walkFolder starts processes for files starting with "az" in the given folder
func walkFolder(folderPath string, ctx context.Context, wg *sync.WaitGroup) {
	os.Chdir(folderPath)
	files, err := os.ReadDir(".")
	if err != nil {
		log.Fatal(err)
	}

	for _, file := range files {
		// Check if filename starts with "oc-"
		if strings.HasPrefix(file.Name(), "oc-") {
			// Prepare the command to start the process

			wg.Add(1)
			go startProcess(ctx, file.Name(), wg)
		}
		// find
		if strings.HasPrefix(strings.ToLower(file.Name()), "caddy") && strings.ToLower(file.Name()) != "caddyfile" {
			runtime := findCaddyRuntime(".")
			if runtime != "" {
				// Prepare the command to start the process
				wg.Add(1)
				go startProcess(ctx, "./"+runtime, wg, "run")
			}
		}
	}
}

// startProcess runs the given program and stores a reference to it for later control
func startProcess(ctx context.Context, program string, wg *sync.WaitGroup, args ...string) {
	defer wg.Done()

	cmd := exec.CommandContext(ctx, program, args...)

	mu.Lock()
	processes = append(processes, cmd)
	mu.Unlock()

	var outBuf, errBuf bytes.Buffer
	cmd.Stdout = &outBuf
	cmd.Stderr = &errBuf
	err := cmd.Start()
	if err != nil {
		log.Printf("Failed to start process: %s", program)
		return
	}

	log.Printf("Started process: %s", program)

	fmt.Print(outBuf.String())
	fmt.Print(errBuf.String())

	err = cmd.Wait()
	if err != nil {
		log.Printf("Process finished with error: %v", err)
	} else {
		log.Printf("Process finished successfully: %s", program)
	}
	fmt.Print(outBuf.String())
	fmt.Print(errBuf.String())
}

// stopAllProcesses stops all the started processes
func stopAllProcesses() {
	mu.Lock()
	defer mu.Unlock()

	for _, cmd := range processes {
		if cmd.Process != nil {
			log.Printf("Killing process with PID %d", cmd.Process.Pid)
			err := cmd.Process.Kill()
			if err != nil {
				log.Printf("Failed to kill process with PID %d: %v", cmd.Process.Pid, err)
			}
		}
	}
	processes = []*exec.Cmd{}
}

func findCaddyRuntime(folder string) string {
	var foundFile string

	err := filepath.WalkDir(folder, func(path string, d fs.DirEntry, err error) error {
		if err != nil {
			return err
		}
		// Check if it's a file (not a directory)
		if !d.IsDir() {
			filename := d.Name()
			// Check if the filename starts with "caddy" (case-insensitive) and is not "CaddyFile"
			if strings.HasPrefix(strings.ToLower(filename), "caddy") && strings.ToLower(filename) != "caddyfile" {
				foundFile = filename
				return filepath.SkipDir // Skip further traversal once we find a match
			}
		}
		return nil
	})

	if err != nil {
		fmt.Println("Error:", err)
		return ""
	}

	return foundFile
}

func downloadLatestCaddy(outputFolder string) error {
	// Define the GitHub API URL for the latest Caddy release
	apiURL := "https://api.github.com/repos/caddyserver/caddy/releases/latest"

	// Get the system's OS and architecture
	osName := runtime.GOOS
	archName := runtime.GOARCH

	// Make an HTTP GET request to the GitHub API
	resp, err := http.Get(apiURL)
	if err != nil {
		return fmt.Errorf("failed to get latest Caddy release: %v", err)
	}
	defer resp.Body.Close()

	// Read the response body
	body, err := io.ReadAll(resp.Body)
	if err != nil {
		return fmt.Errorf("failed to read GitHub API response: %v", err)
	}

	// Find the download URL for the current platform (OS + architecture)
	downloadURL := ""
	urlPrefix := fmt.Sprintf("%s_%s", osName, archName)
	lines := strings.Split(string(body), "\n")
	for _, line := range lines {
		if strings.Contains(line, "browser_download_url") && strings.Contains(line, urlPrefix) {
			// Extract the URL
			parts := strings.Split(line, "\"")
			for _, part := range parts {
				if strings.HasPrefix(part, "https://") && strings.Contains(part, urlPrefix) && (strings.HasSuffix(part, ".tar.gz") || strings.HasSuffix(part, ".zip")) {
					downloadURL = part
					break
				}
			}
			if downloadURL != "" {
				break
			}
		}
	}

	if downloadURL == "" {
		return fmt.Errorf("failed to find a suitable Caddy binary for %s_%s", osName, archName)
	}

	// Make an HTTP GET request to download the file
	resp, err = http.Get(downloadURL)
	if err != nil {
		return fmt.Errorf("failed to download Caddy executable: %v", err)
	}
	defer resp.Body.Close()
	suffix := ""
	if strings.Contains(downloadURL, ".tar.gz") {
		suffix = ".tar.gz"
	} else if strings.Contains(downloadURL, ".zip") {
		suffix = ".zip"
	}

	// Create the output file
	outputPath := fmt.Sprintf("%s/caddy"+suffix, strings.TrimRight(outputFolder, "/"))
	outFile, err := os.Create(outputPath)
	if err != nil {
		return fmt.Errorf("failed to create output file: %v", err)
	}
	defer outFile.Close()

	// Write the response body to the output file
	_, err = io.Copy(outFile, resp.Body)
	if err != nil {
		return fmt.Errorf("failed to write to output file: %v", err)
	}

	// Extract the downloaded file
	if strings.Contains(downloadURL, ".tar.gz") {
		err = exec.Command("tar", "-xzf", outputPath, "-C", outputFolder).Run()
		if err != nil {
			return fmt.Errorf("failed to extract tar.gz file: %v", err)
		}
	} else if strings.Contains(downloadURL, ".zip") {
		err = exec.Command("unzip", outputPath, "-d", outputFolder).Run()
		if err != nil {
			return fmt.Errorf("failed to extract zip file: %v", err)
		}
	}

	// Remove the downloaded file
	err = os.Remove(outputPath)
	if err != nil {
		return fmt.Errorf("failed to remove: %v", err)
	}

	fmt.Printf("Caddy downloaded successfully to %s\n", outputPath)
	return nil
}
