package apt

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

	"github.com/sirupsen/logrus"
)

// PackageSpec represents a Debian package specification
type PackageSpec struct {
	Name    string `json:"name"`
	Version string `json:"version,omitempty"`
	Arch    string `json:"arch,omitempty"`
}

// RepoConfig represents an APT repository configuration
type RepoConfig struct {
	BaseURL    string   `json:"baseurl"`
	Components []string `json:"components,omitempty"`
	Arch       string   `json:"arch,omitempty"`
	Priority   int      `json:"priority,omitempty"`
}

// DepsolveResult contains the result of package dependency resolution
type DepsolveResult struct {
	Packages []PackageSpec `json:"packages"`
	Repos    []RepoConfig  `json:"repos"`
}

// Solver handles APT-based package dependency resolution
type Solver struct {
	cacheRoot string
	arch      string
	repos     []RepoConfig
}

// NewSolver creates a new APT solver instance
func NewSolver(cacheRoot, arch string, repos []RepoConfig) (*Solver, error) {
	// Ensure cache directory exists
	if err := os.MkdirAll(cacheRoot, 0755); err != nil {
		return nil, fmt.Errorf("cannot create cache directory %s: %w", cacheRoot, err)
	}

	return &Solver{
		cacheRoot: cacheRoot,
		arch:      arch,
		repos:     repos,
	}, nil
}

// Depsolve resolves package dependencies using APT
func (s *Solver) Depsolve(packages []string, seed int) (*DepsolveResult, error) {
	logrus.Debugf("Depsolving packages: %v", packages)
	
	// Try real APT first, fall back to mock if not available
	result, err := s.realDepsolve(packages)
	if err != nil {
		logrus.Warnf("Real APT depsolve failed, using mock: %v", err)
		return s.mockDepsolve(packages)
	}
	
	// If real APT succeeds, return its result
	return result, nil
}

// realDepsolve uses actual APT commands to resolve dependencies
func (s *Solver) realDepsolve(packages []string) (*DepsolveResult, error) {
	logrus.Debugf("Using real APT depsolve for packages: %v", packages)
	
	// Create temporary APT configuration
	aptConf, err := s.createAptConf()
	if err != nil {
		return nil, fmt.Errorf("cannot create APT configuration: %w", err)
	}
	// Keep the file for debugging
	// defer os.Remove(aptConf)

	// Use apt-cache to resolve dependencies
	cmd := exec.Command("apt-cache", "depends", "--no-recommends", "--no-suggests")
	cmd.Env = append(os.Environ(), fmt.Sprintf("APT_CONFIG=%s", aptConf))
	cmd.Args = append(cmd.Args, packages...)
	
	logrus.Debugf("Running command: %s %v", cmd.Path, cmd.Args)
	logrus.Debugf("APT_CONFIG=%s", aptConf)
	
	output, err := cmd.Output()
	if err != nil {
		logrus.Debugf("apt-cache command failed: %v", err)
		return nil, fmt.Errorf("apt-cache depends failed: %w", err)
	}

	// Parse the output to extract package names
	resolvedPackages := s.parseAptOutput(string(output))
	
	// Get package versions
	packageSpecs := make([]PackageSpec, 0, len(resolvedPackages))
	for _, pkg := range resolvedPackages {
		version, err := s.getPackageVersion(pkg, aptConf)
		if err != nil {
			logrus.Warnf("Cannot get version for package %s: %v", pkg, err)
			version = ""
		}
		
		packageSpecs = append(packageSpecs, PackageSpec{
			Name:    pkg,
			Version: version,
			Arch:    s.arch,
		})
	}

	return &DepsolveResult{
		Packages: packageSpecs,
		Repos:    s.repos,
	}, nil
}

// mockDepsolve provides a mock implementation for testing
func (s *Solver) mockDepsolve(packages []string) (*DepsolveResult, error) {
	logrus.Debugf("Using mock APT depsolve for packages: %v", packages)
	
	// Create mock resolved packages
	packageSpecs := make([]PackageSpec, 0, len(packages))
	for _, pkg := range packages {
		packageSpecs = append(packageSpecs, PackageSpec{
			Name:    pkg,
			Version: "1.0.0",
			Arch:    s.arch,
		})
	}

	return &DepsolveResult{
		Packages: packageSpecs,
		Repos:    s.repos,
	}, nil
}

// createAptConf creates a temporary APT configuration file
func (s *Solver) createAptConf() (string, error) {
	confFile := filepath.Join(s.cacheRoot, "apt.conf")
	
	// APT configuration directives
	conf := "APT::Architecture \"" + s.arch + "\";\n"
	conf += "APT::Get::Assume-Yes \"true\";\n"
	conf += "APT::Get::AllowUnauthenticated \"true\";\n"
	conf += "APT::Cache::Generate \"true\";\n"
	
	// Create sources.list file
	sourcesFile := filepath.Join(s.cacheRoot, "sources.list")
	sources := ""
	for _, repo := range s.repos {
		sources += fmt.Sprintf("deb [arch=%s] %s %s\n", s.arch, repo.BaseURL, strings.Join(repo.Components, " "))
	}
	
	// Add sources.list to APT configuration
	conf += fmt.Sprintf("Dir::Etc::SourceList \"%s\";\n", sourcesFile)
	
	// Write sources.list
	if err := os.WriteFile(sourcesFile, []byte(sources), 0644); err != nil {
		return "", fmt.Errorf("cannot write sources.list: %w", err)
	}

	// Write APT configuration
	if err := os.WriteFile(confFile, []byte(conf), 0644); err != nil {
		return "", fmt.Errorf("cannot write APT configuration: %w", err)
	}

	return confFile, nil
}

// parseAptOutput parses the output from apt-cache depends
func (s *Solver) parseAptOutput(output string) []string {
	packages := make(map[string]bool)
	lines := strings.Split(output, "\n")
	
	// Skip dependency relationship keywords
	skipKeywords := map[string]bool{
		"Depends:": true,
		"PreDepends:": true,
		"Breaks:": true,
		"Replaces:": true,
		"Conflicts:": true,
		"Recommends:": true,
		"Suggests:": true,
		"Enhances:": true,
		"|Depends": true,
		"|PreDepends": true,
		"Enhances": true,
	}
	
	for _, line := range lines {
		line = strings.TrimSpace(line)
		if line == "" {
			continue
		}
		
		// Skip dependency relationship keywords
		skip := false
		for keyword := range skipKeywords {
			if strings.HasPrefix(line, keyword) {
				skip = true
				break
			}
		}
		if skip {
			continue
		}
		
		// Extract package name (remove version constraints and indentation)
		if strings.Contains(line, " ") {
			parts := strings.Fields(line)
			if len(parts) > 0 {
				pkg := strings.TrimSuffix(parts[0], ":")
				// Only add if it looks like a real package name
				if !strings.Contains(pkg, ":") && !strings.Contains(pkg, "<") && !strings.Contains(pkg, ">") && !strings.Contains(pkg, "|") {
					packages[pkg] = true
				}
			}
		} else if line != "" && !strings.Contains(line, ":") && !strings.Contains(line, "<") && !strings.Contains(line, ">") && !strings.Contains(line, "|") {
			// Single word that's not a keyword
			packages[line] = true
		}
	}

	result := make([]string, 0, len(packages))
	for pkg := range packages {
		result = append(result, pkg)
	}
	
	return result
}

// getPackageVersion gets the version of a specific package
func (s *Solver) getPackageVersion(pkg, aptConf string) (string, error) {
	cmd := exec.Command("apt-cache", "show", pkg)
	cmd.Env = append(os.Environ(), fmt.Sprintf("APT_CONFIG=%s", aptConf))
	
	output, err := cmd.Output()
	if err != nil {
		return "", err
	}

	lines := strings.Split(string(output), "\n")
	for _, line := range lines {
		if strings.HasPrefix(line, "Version:") {
			return strings.TrimSpace(strings.TrimPrefix(line, "Version:")), nil
		}
	}

	return "", fmt.Errorf("version not found for package %s", pkg)
}
