package apt

import (
	"os"
	"path/filepath"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// createTestSolver creates a test solver with default configuration
func createTestSolver(t *testing.T) *Solver {
	tmpDir := t.TempDir()
	repos := []RepoConfig{
		{
			BaseURL:    "http://deb.debian.org/debian",
			Components: []string{"main"},
			Priority:   500,
		},
	}
	solver, err := NewSolver(tmpDir, "amd64", repos)
	require.NoError(t, err)
	return solver
}

func TestSolverCreation(t *testing.T) {
	// Test creating a new solver
	solver := createTestSolver(t)
	assert.NotNil(t, solver)
	assert.NotNil(t, solver.repos)
}

func TestMockDepsolve(t *testing.T) {
	solver := createTestSolver(t)
	
	// Test mock depsolve with empty package list
	result, err := solver.mockDepsolve([]string{})
	assert.NoError(t, err)
	assert.NotNil(t, result)
	assert.Empty(t, result.Packages)

	// Test mock depsolve with some packages
	packages := []string{"base-files", "systemd", "linux-image-amd64"}
	result, err = solver.mockDepsolve(packages)
	assert.NoError(t, err)
	assert.NotNil(t, result)
	
	// Check that we got PackageSpec structs with correct names
	expectedNames := []string{"base-files", "systemd", "linux-image-amd64"}
	actualNames := make([]string, len(result.Packages))
	for i, pkg := range result.Packages {
		actualNames[i] = pkg.Name
	}
	assert.Equal(t, expectedNames, actualNames)
}

func TestDepsolve(t *testing.T) {
	solver := createTestSolver(t)
	
	// Test depsolve with empty package list
	result, err := solver.Depsolve([]string{}, 0)
	assert.NoError(t, err)
	assert.NotNil(t, result)
	assert.Empty(t, result.Packages)

	// Test depsolve with some packages (should fall back to mock)
	packages := []string{"base-files", "systemd", "linux-image-amd64"}
	result, err = solver.Depsolve(packages, 0)
	assert.NoError(t, err)
	assert.NotNil(t, result)
	
	// Check that we got PackageSpec structs with correct names
	expectedNames := []string{"base-files", "systemd", "linux-image-amd64"}
	actualNames := make([]string, len(result.Packages))
	for i, pkg := range result.Packages {
		actualNames[i] = pkg.Name
	}
	assert.Equal(t, expectedNames, actualNames)
}

func TestParseAptOutput(t *testing.T) {
	solver := createTestSolver(t)
	
	// Test with empty output
	result := solver.parseAptOutput("")
	assert.Empty(t, result)

	// Test with valid package names
	output := `base-files
systemd
linux-image-amd64
grub-common`
	result = solver.parseAptOutput(output)
	expected := []string{"base-files", "systemd", "linux-image-amd64", "grub-common"}
	assert.ElementsMatch(t, expected, result)

	// Test with dependency keywords (should be filtered out)
	output = `Depends: base-files
PreDepends: systemd
Breaks: old-package
Replaces: old-package
Conflicts: conflicting-package
Recommends: recommended-package
Suggests: suggested-package
Enhances: enhanced-package
|Depends: alternative-package
|PreDepends: alternative-pre-depends
Enhances: enhanced-package
base-files
systemd`
	result = solver.parseAptOutput(output)
	expected = []string{"base-files", "systemd"}
	assert.ElementsMatch(t, expected, result)

	// Test with package names containing special characters (should be filtered out)
	output = `base-files
<virtual-package>
>version-constraint
|alternative-package
base-files:amd64
systemd`
	result = solver.parseAptOutput(output)
	expected = []string{"base-files", "systemd"}
	assert.ElementsMatch(t, expected, result)

	// Test with mixed content
	output = `Depends: base-files
systemd
Breaks: old-package
linux-image-amd64
|Depends: alternative
grub-common
Enhances: enhanced
bootc`
	result = solver.parseAptOutput(output)
	expected = []string{"systemd", "linux-image-amd64", "grub-common", "bootc"}
	assert.ElementsMatch(t, expected, result)
}

func TestCreateAptConf(t *testing.T) {
	solver := createTestSolver(t)
	
	// Test creating apt configuration
	aptConfPath, err := solver.createAptConf()
	assert.NoError(t, err)
	assert.NotEmpty(t, aptConfPath)
	
	// Check that apt.conf was created
	assert.FileExists(t, aptConfPath)
	
	// Check that sources.list was created in the same directory as apt.conf
	sourcesListPath := filepath.Join(filepath.Dir(aptConfPath), "sources.list")
	assert.FileExists(t, sourcesListPath)
	
	// Read and verify apt.conf content
	aptConfContent, err := os.ReadFile(aptConfPath)
	assert.NoError(t, err)
	aptConfStr := string(aptConfContent)
	assert.Contains(t, aptConfStr, "Dir::Etc::SourceList")
	assert.Contains(t, aptConfStr, "sources.list")
	
	// Read and verify sources.list content
	sourcesListContent, err := os.ReadFile(sourcesListPath)
	assert.NoError(t, err)
	sourcesListStr := string(sourcesListContent)
	assert.Contains(t, sourcesListStr, "deb.debian.org")
	assert.Contains(t, sourcesListStr, "main")
}

func TestGetPackageVersion(t *testing.T) {
	solver := createTestSolver(t)
	
	// Test getting version for a package (may get real version or fall back to mock)
	version, err := solver.getPackageVersion("base-files", "")
	// Don't assert specific version since it depends on system state
	assert.NoError(t, err)
	assert.NotEmpty(t, version)
	
	// Test getting version for non-existent package (should fall back to mock)
	version, err = solver.getPackageVersion("non-existent-package", "")
	// This might fail with real apt-cache, so we just check it doesn't panic
	_ = version
	_ = err
}

func TestRealDepsolve(t *testing.T) {
	solver := createTestSolver(t)
	
	// Test real depsolve (may fail if apt-cache is not available)
	result, err := solver.realDepsolve([]string{"base-files"})
	
	// We don't assert success here because apt-cache may not be available in test environment
	// The important thing is that it doesn't panic
	// Result might be nil if apt-cache fails, which is acceptable
	_ = result
	_ = err // Suppress unused variable warning
}

func TestSolverWithCustomRepos(t *testing.T) {
	// Test creating solver with custom repositories
	repos := []RepoConfig{
		{
			BaseURL:    "http://deb.debian.org/debian",
			Components: []string{"main"},
			Priority:   500,
		},
		{
			BaseURL:    "https://git.raines.xyz/api/packages/particle-os/debian",
			Components: []string{"trixie", "main"},
			Priority:   400,
		},
	}
	
	solver := &Solver{repos: repos}
	assert.NotNil(t, solver)
	assert.Equal(t, repos, solver.repos)
}

func TestDepsolveWithLargePackageList(t *testing.T) {
	solver := createTestSolver(t)
	
	// Test with a large package list
	packages := make([]string, 100)
	for i := 0; i < 100; i++ {
		packages[i] = "package-" + string(rune('a'+i%26))
	}
	
	result, err := solver.Depsolve(packages, 0)
	assert.NoError(t, err)
	assert.NotNil(t, result)
	
	// Check that we got PackageSpec structs with correct names
	actualNames := make([]string, len(result.Packages))
	for i, pkg := range result.Packages {
		actualNames[i] = pkg.Name
	}
	assert.Equal(t, packages, actualNames)
}

func TestParseAptOutputEdgeCases(t *testing.T) {
	solver := createTestSolver(t)
	
	// Test with only whitespace
	result := solver.parseAptOutput("   \n\t  \n  ")
	assert.Empty(t, result)
	
	// Test with only dependency keywords
	output := `Depends: package1
PreDepends: package2
Breaks: package3
Replaces: package4
Conflicts: package5
Recommends: package6
Suggests: package7
Enhances: package8
|Depends: package9
|PreDepends: package10`
	result = solver.parseAptOutput(output)
	assert.Empty(t, result)
	
	// Test with only special characters
	output = `<virtual>
>constraint
|alternative
package:arch`
	result = solver.parseAptOutput(output)
	assert.Empty(t, result)
	
	// Test with mixed valid and invalid
	output = `valid-package
Depends: invalid
another-valid
<virtual>
yet-another-valid`
	result = solver.parseAptOutput(output)
	expected := []string{"valid-package", "another-valid", "yet-another-valid"}
	assert.ElementsMatch(t, expected, result)
}
