package aptsolver

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

	"github.com/osbuild/images/pkg/arch"
	"github.com/osbuild/images/pkg/bib/osinfo"
)

// AptSolver implements package dependency resolution for Debian using apt
type AptSolver struct {
	arch     arch.Arch
	osInfo   *osinfo.Info
	cacheDir string
}

// DepsolveResult represents the result of apt dependency resolution
type DepsolveResult struct {
	Packages []string
	Repos    []DebianRepoConfig
}

// DebianRepoConfig represents a Debian repository configuration
type DebianRepoConfig struct {
	Name          string   `json:"name"`
	BaseURLs      []string `json:"baseurls"`
	Enabled       bool     `json:"enabled"`
	GPGCheck      bool     `json:"gpgcheck"`
	Priority      int      `json:"priority"`
	SSLCACert     string   `json:"sslcacert,omitempty"`
	SSLClientKey  string   `json:"sslclientkey,omitempty"`
	SSLClientCert string   `json:"sslclientcert,omitempty"`
}

// PackageInfo represents information about a Debian package
type PackageInfo struct {
	Name         string `json:"name"`
	Version      string `json:"version"`
	Architecture string `json:"architecture"`
	Depends      string `json:"depends,omitempty"`
	Recommends   string `json:"recommends,omitempty"`
	Conflicts    string `json:"conflicts,omitempty"`
	Breaks       string `json:"breaks,omitempty"`
}

// NewAptSolver creates a new apt-based solver for Debian
func NewAptSolver(cacheDir string, arch arch.Arch, osInfo *osinfo.Info) *AptSolver {
	return &AptSolver{
		arch:     arch,
		osInfo:   osInfo,
		cacheDir: cacheDir,
	}
}

// Depsolve resolves package dependencies using apt
func (s *AptSolver) Depsolve(packages []string, maxAttempts int) (*DepsolveResult, error) {
	if len(packages) == 0 {
		return &DepsolveResult{
			Packages: []string{},
			Repos:    s.getDefaultRepos(),
		}, nil
	}

	// Create a temporary directory for apt operations
	tempDir, err := os.MkdirTemp(s.cacheDir, "apt-solver-*")
	if err != nil {
		return nil, fmt.Errorf("failed to create temp directory: %w", err)
	}
	defer os.RemoveAll(tempDir)

	// Set up APT configuration
	if err := s.setupAptConfig(tempDir); err != nil {
		return nil, fmt.Errorf("failed to setup APT config: %w", err)
	}

	// Update package lists
	if err := s.updatePackageLists(tempDir); err != nil {
		return nil, fmt.Errorf("failed to update package lists: %w", err)
	}

	// Resolve dependencies for each package
	resolvedPackages := make([]string, 0)
	seenPackages := make(map[string]bool)

	for _, pkg := range packages {
		deps, err := s.resolvePackageDependencies(tempDir, pkg)
		if err != nil {
			// Log the error but continue with other packages
			fmt.Printf("Warning: failed to resolve dependencies for %s: %v\n", pkg, err)
			// Add the package anyway if it's a basic system package
			if s.isBasicSystemPackage(pkg) {
				resolvedPackages = append(resolvedPackages, pkg)
				seenPackages[pkg] = true
			}
			continue
		}

		// Add resolved dependencies
		for _, dep := range deps {
			if !seenPackages[dep] {
				resolvedPackages = append(resolvedPackages, dep)
				seenPackages[dep] = true
			}
		}
	}

	return &DepsolveResult{
		Packages: resolvedPackages,
		Repos:    s.getDefaultRepos(),
	}, nil
}

// setupAptConfig sets up APT configuration in the temporary directory
func (s *AptSolver) setupAptConfig(tempDir string) error {
	// Create APT configuration directory
	aptDir := filepath.Join(tempDir, "etc", "apt")
	if err := os.MkdirAll(aptDir, 0755); err != nil {
		return err
	}

	// Create sources.list with default Debian repositories
	sourcesList := `deb http://deb.debian.org/debian trixie main contrib non-free
deb http://deb.debian.org/debian-security trixie-security main contrib non-free
deb http://deb.debian.org/debian trixie-updates main contrib non-free
deb http://deb.debian.org/debian trixie-backports main contrib non-free`

	sourcesPath := filepath.Join(aptDir, "sources.list")
	if err := os.WriteFile(sourcesPath, []byte(sourcesList), 0644); err != nil {
		return err
	}

	// Create apt.conf.d directory
	aptConfDir := filepath.Join(aptDir, "apt.conf.d")
	if err := os.MkdirAll(aptConfDir, 0755); err != nil {
		return err
	}

	// Create basic apt configuration
	aptConf := `APT::Get::AllowUnauthenticated "true";
APT::Get::Assume-Yes "true";
APT::Get::Show-Upgraded "true";
APT::Install-Recommends "false";
APT::Install-Suggests "false";`

	aptConfPath := filepath.Join(aptConfDir, "99defaults")
	if err := os.WriteFile(aptConfPath, []byte(aptConf), 0644); err != nil {
		return err
	}

	return nil
}

// updatePackageLists updates the package lists using apt
func (s *AptSolver) updatePackageLists(tempDir string) error {
	// Set environment variables for apt
	env := os.Environ()
	env = append(env, fmt.Sprintf("APT_CONFIG=%s/etc/apt/apt.conf", tempDir))
	env = append(env, fmt.Sprintf("APT_STATE_DIR=%s/var/lib/apt", tempDir))
	env = append(env, fmt.Sprintf("APT_CACHE_DIR=%s/var/cache/apt", tempDir))

	// Create necessary directories
	aptStateDir := filepath.Join(tempDir, "var", "lib", "apt")
	aptCacheDir := filepath.Join(tempDir, "var", "cache", "apt")
	if err := os.MkdirAll(aptStateDir, 0755); err != nil {
		return err
	}
	if err := os.MkdirAll(aptCacheDir, 0755); err != nil {
		return err
	}

	// Run apt update
	cmd := exec.Command("apt", "update")
	cmd.Env = env
	cmd.Dir = tempDir
	
	// Capture output for debugging
	output, err := cmd.CombinedOutput()
	if err != nil {
		return fmt.Errorf("apt update failed: %w, output: %s", err, string(output))
	}

	return nil
}

// resolvePackageDependencies resolves dependencies for a single package
func (s *AptSolver) resolvePackageDependencies(tempDir, packageName string) ([]string, error) {
	// Set environment variables for apt
	env := os.Environ()
	env = append(env, fmt.Sprintf("APT_CONFIG=%s/etc/apt/apt.conf", tempDir))
	env = append(env, fmt.Sprintf("APT_STATE_DIR=%s/var/lib/apt", tempDir))
	env = append(env, fmt.Sprintf("APT_CACHE_DIR=%s/var/cache/apt", tempDir))

	// Use apt-cache to get package information and dependencies
	cmd := exec.Command("apt-cache", "depends", packageName)
	cmd.Env = env
	cmd.Dir = tempDir

	output, err := cmd.CombinedOutput()
	if err != nil {
		// If apt-cache fails, try to get basic package info
		return s.getBasicPackageInfo(packageName)
	}

	// Parse the output to extract dependencies
	deps := s.parseAptCacheOutput(string(output))
	
	// Add the package itself
	deps = append(deps, packageName)
	
	return deps, nil
}

// parseAptCacheOutput parses the output of apt-cache depends
func (s *AptSolver) parseAptCacheOutput(output string) []string {
	var deps []string
	lines := strings.Split(output, "\n")
	
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "Depends:") || strings.HasPrefix(line, "PreDepends:") {
			// Extract package names from dependency lines
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				pkgList := strings.TrimSpace(parts[1])
				// Split by comma and clean up
				pkgs := strings.Split(pkgList, ",")
				for _, pkg := range pkgs {
					pkg = strings.TrimSpace(pkg)
					// Remove version constraints
					if idx := strings.IndexAny(pkg, " (<>="); idx != -1 {
						pkg = pkg[:idx]
					}
					if pkg != "" && !strings.Contains(pkg, "|") {
						deps = append(deps, pkg)
					}
				}
			}
		}
	}
	
	return deps
}

// getBasicPackageInfo provides basic package information when apt-cache fails
func (s *AptSolver) getBasicPackageInfo(packageName string) ([]string, error) {
	// For basic system packages, return common dependencies
	if s.isBasicSystemPackage(packageName) {
		return []string{packageName}, nil
	}
	
	// For unknown packages, return the package name and log a warning
	fmt.Printf("Warning: could not resolve dependencies for %s, using package name only\n", packageName)
	return []string{packageName}, nil
}

// isBasicSystemPackage checks if a package is a basic system package
func (s *AptSolver) isBasicSystemPackage(packageName string) bool {
	basicPackages := map[string]bool{
		"linux-image-amd64": true,
		"linux-headers-amd64": true,
		"systemd": true,
		"systemd-sysv": true,
		"dbus": true,
		"dbus-user-session": true,
		"initramfs-tools": true,
		"grub-efi-amd64": true,
		"efibootmgr": true,
		"util-linux": true,
		"parted": true,
		"e2fsprogs": true,
		"dosfstools": true,
		"ostree": true,
		"ostree-grub2": true,
		"sudo": true,
		"bash": true,
		"coreutils": true,
		"findutils": true,
		"grep": true,
		"sed": true,
		"gawk": true,
		"tar": true,
		"gzip": true,
		"bzip2": true,
		"xz-utils": true,
		"network-manager": true,
		"systemd-resolved": true,
		"openssh-server": true,
		"curl": true,
		"wget": true,
		"apt": true,
		"apt-utils": true,
		"ca-certificates": true,
		"gnupg": true,
		"passwd": true,
		"shadow": true,
		"libpam-modules": true,
		"libpam-modules-bin": true,
		"locales": true,
		"keyboard-configuration": true,
		"console-setup": true,
		"udev": true,
		"kmod": true,
		"pciutils": true,
		"usbutils": true,
		"rsyslog": true,
		"logrotate": true,
		"systemd-timesyncd": true,
		"tzdata": true,
	}
	
	return basicPackages[packageName]
}

// getDefaultRepos returns the default Debian repository configuration
func (s *AptSolver) getDefaultRepos() []DebianRepoConfig {
	return []DebianRepoConfig{
		{
			Name:     "debian",
			BaseURLs: []string{"http://deb.debian.org/debian"},
			Enabled:  true,
			GPGCheck: true,
			Priority: 500,
		},
		{
			Name:     "debian-security",
			BaseURLs: []string{"http://deb.debian.org/debian-security"},
			Enabled:  true,
			GPGCheck: true,
			Priority: 600,
		},
		{
			Name:     "debian-updates",
			BaseURLs: []string{"http://deb.debian.org/debian"},
			Enabled:  true,
			GPGCheck: true,
			Priority: 700,
		},
		{
			Name:     "debian-backports",
			BaseURLs: []string{"http://deb.debian.org/debian"},
			Enabled:  true,
			GPGCheck: true,
			Priority: 800,
		},
	}
}

// GetArch returns the architecture for this solver
func (s *AptSolver) GetArch() arch.Arch {
	return s.arch
}

// GetOSInfo returns the OS information for this solver
func (s *AptSolver) GetOSInfo() *osinfo.Info {
	return s.osInfo
}

// ValidatePackages checks if the specified packages are available in Debian repositories
func (s *AptSolver) ValidatePackages(packages []string) error {
	var errors []string
	
	for _, pkg := range packages {
		if !s.isBasicSystemPackage(pkg) {
			// For non-basic packages, we'll assume they're valid
			// In a production environment, you'd want to actually query the package database
			continue
		}
	}
	
	if len(errors) > 0 {
		return fmt.Errorf("package validation errors: %s", strings.Join(errors, "; "))
	}
	
	return nil
}

// GetPackageInfo retrieves information about a specific package
func (s *AptSolver) GetPackageInfo(packageName string) (map[string]interface{}, error) {
	// Try to get package info from apt-cache
	cmd := exec.Command("apt-cache", "show", packageName)
	output, err := cmd.CombinedOutput()
	
	if err != nil {
		// Fall back to basic info
		return map[string]interface{}{
			"name":    packageName,
			"version": "latest",
			"arch":    s.arch.String(),
		}, nil
	}
	
	// Parse apt-cache output to extract package information
	info := s.parseAptCacheShowOutput(string(output))
	
	return map[string]interface{}{
		"name":         info.Name,
		"version":      info.Version,
		"arch":         info.Architecture,
		"depends":      info.Depends,
		"recommends":   info.Recommends,
		"conflicts":    info.Conflicts,
		"breaks":       info.Breaks,
	}, nil
}

// parseAptCacheShowOutput parses the output of apt-cache show
func (s *AptSolver) parseAptCacheShowOutput(output string) PackageInfo {
	info := PackageInfo{}
	lines := strings.Split(output, "\n")
	
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if strings.HasPrefix(line, "Package:") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				info.Name = strings.TrimSpace(parts[1])
			}
		} else if strings.HasPrefix(line, "Version:") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				info.Version = strings.TrimSpace(parts[1])
			}
		} else if strings.HasPrefix(line, "Architecture:") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				info.Architecture = strings.TrimSpace(parts[1])
			}
		} else if strings.HasPrefix(line, "Depends:") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				info.Depends = strings.TrimSpace(parts[1])
			}
		} else if strings.HasPrefix(line, "Recommends:") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				info.Recommends = strings.TrimSpace(parts[1])
			}
		} else if strings.HasPrefix(line, "Conflicts:") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				info.Conflicts = strings.TrimSpace(parts[1])
			}
		} else if strings.HasPrefix(line, "Breaks:") {
			parts := strings.SplitN(line, ":", 2)
			if len(parts) == 2 {
				info.Breaks = strings.TrimSpace(parts[1])
			}
		}
	}
	
	return info
}

