package postgres

import (
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/mholt/archiver/v4"
)

const (
	// DefaultGoVersion is the default Go version to install
	DefaultGoVersion = "1.22.2"
)

// GoInstaller handles Go installation checks and installation
type GoInstaller struct {
	Version string
}

// NewGoInstaller creates a new Go installer with the default version
func NewGoInstaller() *GoInstaller {
	return &GoInstaller{
		Version: DefaultGoVersion,
	}
}

// WithVersion sets the Go version to install
func (g *GoInstaller) WithVersion(version string) *GoInstaller {
	g.Version = version
	return g
}

// IsGoInstalled checks if Go is installed and available
func (g *GoInstaller) IsGoInstalled() bool {
	// Check if go command is available
	cmd := exec.Command("go", "version")
	if err := cmd.Run(); err != nil {
		return false
	}
	return true
}

// GetGoVersion gets the installed Go version
func (g *GoInstaller) GetGoVersion() (string, error) {
	cmd := exec.Command("go", "version")
	output, err := cmd.Output()
	if err != nil {
		return "", fmt.Errorf("failed to get Go version: %w", err)
	}

	// Parse go version output (format: "go version go1.x.x ...")
	version := strings.TrimSpace(string(output))
	parts := strings.Split(version, " ")
	if len(parts) < 3 {
		return "", fmt.Errorf("unexpected go version output format: %s", version)
	}

	// Return just the version number without the "go" prefix
	return strings.TrimPrefix(parts[2], "go"), nil
}

// InstallGo installs Go if it's not already installed and returns the path to the Go executable
func (g *GoInstaller) InstallGo() (string, error) {
	// First check if Go is available in PATH
	if path, err := exec.LookPath("go"); err == nil {
		// Test if it works
		cmd := exec.Command(path, "version")
		if output, err := cmd.Output(); err == nil {
			fmt.Printf("Found working Go in PATH: %s, version: %s\n", path, strings.TrimSpace(string(output)))
			return path, nil
		}
	}
	// Default Go installation location
	var installDir string = "/usr/local"
	var goExePath string = filepath.Join(installDir, "go", "bin", "go")

	// Check if Go is already installed by checking the binary directly
	if _, err := os.Stat(goExePath); err == nil {
		version, err := g.GetGoVersion()
		if err == nil {
			fmt.Printf("Go is already installed (version %s), skipping installation\n", version)
			return goExePath, nil
		}
	}

	// Also check if Go is available in PATH as a fallback
	if g.IsGoInstalled() {
		path, err := exec.LookPath("go")
		if err == nil {
			version, err := g.GetGoVersion()
			if err == nil {
				fmt.Printf("Go is already installed (version %s) at %s, skipping installation\n", version, path)
				return path, nil
			}
		}
	}

	fmt.Printf("Installing Go version %s...\n", g.Version)

	// Determine architecture and OS
	goOS := runtime.GOOS
	goArch := runtime.GOARCH

	// Construct download URL
	downloadURL := fmt.Sprintf("https://golang.org/dl/go%s.%s-%s.tar.gz", g.Version, goOS, goArch)

	// Create a temporary directory for download
	tempDir, err := os.MkdirTemp("", "go-install-")
	if err != nil {
		return "", fmt.Errorf("failed to create temporary directory: %w", err)
	}
	defer os.RemoveAll(tempDir)

	// Download Go tarball
	tarballPath := filepath.Join(tempDir, "go.tar.gz")
	if err := downloadFile(downloadURL, tarballPath); err != nil {
		return "", fmt.Errorf("failed to download Go: %w", err)
	}

	// Install directory - typically /usr/local for Linux/macOS

	// Check if existing Go installation exists and remove it
	existingGoDir := filepath.Join(installDir, "go")
	if _, err := os.Stat(existingGoDir); err == nil {
		fmt.Printf("Removing existing Go installation at %s\n", existingGoDir)
		if err := os.RemoveAll(existingGoDir); err != nil {
			return "", fmt.Errorf("failed to remove existing Go installation: %w", err)
		}
	}

	// Extract tarball to install directory
	fmt.Printf("Extracting Go to %s\n", installDir)
	err = extractTarGz(tarballPath, installDir)
	if err != nil {
		return "", fmt.Errorf("failed to extract Go tarball: %w", err)
	}

	// Verify installation
	var goExePathVerify = filepath.Join(installDir, "go", "bin", "go") // Use = instead of := to avoid variable shadowing

	// Check if the Go binary exists
	var statErr error
	_, statErr = os.Stat(goExePathVerify)
	if statErr != nil {
		return "", fmt.Errorf("Go installation failed - go executable not found at %s", goExePathVerify)
	}

	// Set up environment variables
	fmt.Println("Setting up Go environment variables...")

	// Update PATH in /etc/profile
	profilePath := "/etc/profile"
	profileContent, err := os.ReadFile(profilePath)
	if err != nil {
		return "", fmt.Errorf("failed to read profile: %w", err)
	}

	// Add Go bin to PATH if not already there
	goBinPath := filepath.Join(installDir, "go", "bin")
	if !strings.Contains(string(profileContent), goBinPath) {
		newContent := string(profileContent) + fmt.Sprintf("\n# Added by PostgreSQL builder\nexport PATH=$PATH:%s\n", goBinPath)
		if err := os.WriteFile(profilePath, []byte(newContent), 0644); err != nil {
			return "", fmt.Errorf("failed to update profile: %w", err)
		}
	}

	fmt.Printf("✅ Go %s installed successfully!\n", g.Version)
	return goExePath, nil
}

// Helper function to extract tarball
func extractTarGz(src, dst string) error {
	return archiver.Unarchive(src, dst)
}
