package installer

import (
	"fmt"
	"log"
	"os/exec"
	"regexp"
	"strings"
	"time"
)

// DNFInstaller handles DNF package installations
type DNFInstaller struct {
	executor *SecureCommandExecutor
}

// NewDNFInstaller creates a new DNF installer
func NewDNFInstaller() *DNFInstaller {
	return &DNFInstaller{
		executor: NewSecureCommandExecutor(),
	}
}

// IsAvailable checks if DNF is available on this system
func (i *DNFInstaller) IsAvailable() bool {
	_, err := exec.LookPath("dnf")
	return err == nil
}

// Install installs packages using DNF
func (i *DNFInstaller) Install(packageName string) (*InstallResult, error) {
	startTime := time.Now()

	// For single package installs, skip makecache to avoid repository conflicts
	// Only run makecache when installing multiple packages (InstallMultiple method)
	installResult, err := i.executor.ExecuteCommand("dnf", []string{"install", "-y", packageName})
	duration := int(time.Since(startTime).Seconds())

	if err != nil {
		return &InstallResult{
			Success:        false,
			ErrorMessage:   fmt.Sprintf("DNF install failed: %v", err),
			Stdout:         installResult.Stdout,
			Stderr:         installResult.Stderr,
			ExitCode:       installResult.ExitCode,
			DurationSeconds: duration,
		}, err
	}

	return &InstallResult{
		Success:        true,
		Stdout:         installResult.Stdout,
		Stderr:         installResult.Stderr,
		ExitCode:       installResult.ExitCode,
		DurationSeconds: duration,
		PackagesInstalled: []string{packageName},
	}, nil
}

// InstallMultiple installs multiple packages using DNF
func (i *DNFInstaller) InstallMultiple(packageNames []string) (*InstallResult, error) {
	if len(packageNames) == 0 {
		return &InstallResult{
			Success:      false,
			ErrorMessage: "No packages specified for installation",
		}, fmt.Errorf("no packages specified")
	}

	startTime := time.Now()

	// Install all packages in one command using secure executor
	args := []string{"install", "-y"}
	args = append(args, packageNames...)

	// Install all packages in one command using secure executor
	installResult, err := i.executor.ExecuteCommand("dnf", args)
	duration := int(time.Since(startTime).Seconds())

	if err != nil {
		return &InstallResult{
			Success:        false,
			ErrorMessage:   fmt.Sprintf("DNF install failed: %v", err),
			Stdout:         installResult.Stdout,
			Stderr:         installResult.Stderr,
			ExitCode:       installResult.ExitCode,
			DurationSeconds: duration,
		}, err
	}

	return &InstallResult{
		Success:        true,
		Stdout:         installResult.Stdout,
		Stderr:         installResult.Stderr,
		ExitCode:       installResult.ExitCode,
		DurationSeconds: duration,
		PackagesInstalled: packageNames,
	}, nil
}

// Upgrade upgrades all packages using DNF
func (i *DNFInstaller) Upgrade() (*InstallResult, error) {
	startTime := time.Now()

	// Refresh package cache first using secure executor
	refreshResult, err := i.executor.ExecuteCommand("dnf", []string{"makecache"})
	if err != nil {
		refreshResult.DurationSeconds = int(time.Since(startTime).Seconds())
		refreshResult.ErrorMessage = fmt.Sprintf("Failed to refresh DNF cache: %v", err)
		return refreshResult, fmt.Errorf("dnf refresh failed: %w", err)
	}

	// Upgrade all packages using secure executor
	upgradeResult, err := i.executor.ExecuteCommand("dnf", []string{"upgrade", "-y"})
	duration := int(time.Since(startTime).Seconds())

	if err != nil {
		return &InstallResult{
			Success:        false,
			ErrorMessage:   fmt.Sprintf("DNF upgrade failed: %v", err),
			Stdout:         upgradeResult.Stdout,
			Stderr:         upgradeResult.Stderr,
			ExitCode:       upgradeResult.ExitCode,
			DurationSeconds: duration,
		}, err
	}

	return &InstallResult{
		Success:        true,
		Stdout:         upgradeResult.Stdout,
		Stderr:         upgradeResult.Stderr,
		ExitCode:       upgradeResult.ExitCode,
		DurationSeconds: duration,
		Action:         "upgrade",
	}, nil
}

// DryRun performs a dry run installation to check dependencies
func (i *DNFInstaller) DryRun(packageName string) (*InstallResult, error) {
	startTime := time.Now()

	// Attempt to refresh package cache, but don't fail if it doesn't work
	// (dry run can still work with slightly stale cache)
	refreshResult, refreshErr := i.executor.ExecuteCommand("dnf", []string{"makecache"})
	if refreshErr != nil {
		// Log refresh attempt but don't fail the dry run
		log.Printf("Warning: DNF makecache failed (continuing with dry run): %v", refreshErr)
	}
	_ = refreshResult // Discard refresh result intentionally

	// Perform dry run installation using secure executor
	installResult, err := i.executor.ExecuteCommand("dnf", []string{"install", "--assumeno", "--downloadonly", packageName})
	duration := int(time.Since(startTime).Seconds())

	// Parse dependencies from the output
	dependencies := i.parseDependenciesFromDNFOutput(installResult.Stdout, packageName)

	if err != nil {
		// DNF dry run may return non-zero exit code even for successful dependency resolution
		// so we check if we were able to parse dependencies
		if len(dependencies) > 0 {
			return &InstallResult{
				Success:        true,
				Stdout:         installResult.Stdout,
				Stderr:         installResult.Stderr,
				ExitCode:       installResult.ExitCode,
				DurationSeconds: duration,
				Dependencies:    dependencies,
				IsDryRun:        true,
				Action:          "dry_run",
			}, nil
		}

		return &InstallResult{
			Success:        false,
			ErrorMessage:   fmt.Sprintf("DNF dry run failed: %v", err),
			Stdout:         installResult.Stdout,
			Stderr:         installResult.Stderr,
			ExitCode:       installResult.ExitCode,
			DurationSeconds: duration,
			IsDryRun:        true,
			Action:          "dry_run",
		}, err
	}

	return &InstallResult{
		Success:        true,
		Stdout:         installResult.Stdout,
		Stderr:         installResult.Stderr,
		ExitCode:       installResult.ExitCode,
		DurationSeconds: duration,
		Dependencies:    dependencies,
		IsDryRun:        true,
		Action:          "dry_run",
	}, nil
}

// parseDependenciesFromDNFOutput extracts dependency package names from DNF dry run output
func (i *DNFInstaller) parseDependenciesFromDNFOutput(output string, packageName string) []string {
	var dependencies []string

	// Regex patterns to find dependencies in DNF output
	patterns := []*regexp.Regexp{
		// Match "Installing dependencies:" section
		regexp.MustCompile(`(?s)Installing dependencies:(.*?)(\n\n|\z|Transaction Summary:)`),
		// Match "Dependencies resolved." section and package list
		regexp.MustCompile(`(?s)Dependencies resolved\.(.*?)(\n\n|\z|Transaction Summary:)`),
		// Match package installation lines
		regexp.MustCompile(`^\s*([a-zA-Z0-9][a-zA-Z0-9+._-]*)\s+[a-zA-Z0-9:.]+(?:\s+[a-zA-Z]+)?$`),
	}

	for _, pattern := range patterns {
		if strings.Contains(pattern.String(), "Installing dependencies:") {
			matches := pattern.FindStringSubmatch(output)
			if len(matches) > 1 {
				// Extract package names from the dependencies section
				lines := strings.Split(matches[1], "\n")
				for _, line := range lines {
					line = strings.TrimSpace(line)
					if line != "" && !strings.Contains(line, "Dependencies") {
						pkg := i.extractPackageNameFromDNFLine(line)
						if pkg != "" {
							dependencies = append(dependencies, pkg)
						}
					}
				}
			}
		}
	}

	// Also look for transaction summary which lists all packages to be installed
	transactionPattern := regexp.MustCompile(`(?s)Transaction Summary:\s*\n\s*Install\s+(\d+) Packages?\s*\n((?:\s+\d+\s+[a-zA-Z0-9+._-]+\s+[a-zA-Z0-9:.]+.*\n?)*)`)
	matches := transactionPattern.FindStringSubmatch(output)
	if len(matches) > 2 {
		installLines := strings.Split(matches[2], "\n")
		for _, line := range installLines {
			line = strings.TrimSpace(line)
			if line != "" {
				pkg := i.extractPackageNameFromDNFLine(line)
				if pkg != "" && pkg != packageName {
					dependencies = append(dependencies, pkg)
				}
			}
		}
	}

	// Remove duplicates
	uniqueDeps := make([]string, 0)
	seen := make(map[string]bool)
	for _, dep := range dependencies {
		if dep != packageName && !seen[dep] {
			seen[dep] = true
			uniqueDeps = append(uniqueDeps, dep)
		}
	}

	return uniqueDeps
}

// extractPackageNameFromDNFLine extracts package name from a DNF output line
func (i *DNFInstaller) extractPackageNameFromDNFLine(line string) string {
	// Remove architecture info if present
	if idx := strings.LastIndex(line, "."); idx > 0 {
		archSuffix := line[idx:]
		if strings.Contains(archSuffix, ".x86_64") || strings.Contains(archSuffix, ".noarch") ||
		   strings.Contains(archSuffix, ".i386") || strings.Contains(archSuffix, ".arm64") {
			line = line[:idx]
		}
	}

	// Extract package name (typically at the start of the line)
	fields := strings.Fields(line)
	if len(fields) > 0 {
		pkg := fields[0]
		// Remove version info if present
		if idx := strings.Index(pkg, "-"); idx > 0 {
			potentialName := pkg[:idx]
			// Check if this looks like a version (contains numbers)
			versionPart := pkg[idx+1:]
			if strings.Contains(versionPart, ".") || regexp.MustCompile(`\d`).MatchString(versionPart) {
				return potentialName
			}
		}
		return pkg
	}

	return ""
}

// UpdatePackage updates a specific package using DNF
func (i *DNFInstaller) UpdatePackage(packageName string) (*InstallResult, error) {
	startTime := time.Now()

	// Update specific package using secure executor
	// Use 'dnf upgrade' instead of 'dnf install' for existing packages
	updateResult, err := i.executor.ExecuteCommand("dnf", []string{"upgrade", "-y", packageName})
	duration := int(time.Since(startTime).Seconds())

	if err != nil {
		return &InstallResult{
			Success:        false,
			ErrorMessage:   fmt.Sprintf("DNF upgrade failed: %v", err),
			Stdout:         updateResult.Stdout,
			Stderr:         updateResult.Stderr,
			ExitCode:       updateResult.ExitCode,
			DurationSeconds: duration,
		}, err
	}

	return &InstallResult{
		Success:        true,
		Stdout:         updateResult.Stdout,
		Stderr:         updateResult.Stderr,
		ExitCode:       updateResult.ExitCode,
		DurationSeconds: duration,
		PackagesInstalled: []string{packageName},
		Action:         "upgrade",
	}, nil
}

// GetPackageType returns type of packages this installer handles
func (i *DNFInstaller) GetPackageType() string {
	return "dnf"
}