package deps

import (
	"fmt"
	"testing"
)

// MockPackageRepository for testing
type MockPackageRepository struct {
	packages map[string]*PackageInfo
}

func NewMockPackageRepository(packages map[string]*PackageInfo) *MockPackageRepository {
	return &MockPackageRepository{packages: packages}
}

func (m *MockPackageRepository) GetInstalled() (map[string]*PackageInfo, error) {
	result := make(map[string]*PackageInfo)
	for k, v := range m.packages {
		result[k] = v
	}
	return result, nil
}

func (m *MockPackageRepository) GetPackage(name string) (*PackageInfo, error) {
	pkg, exists := m.packages[name]
	if !exists {
		return nil, fmt.Errorf("package %s not found", name)
	}
	return pkg, nil
}

func (m *MockPackageRepository) SearchPackages(query string) ([]*PackageInfo, error) {
	var results []*PackageInfo
	for _, pkg := range m.packages {
		if pkg.Name == query {
			results = append(results, pkg)
		}
	}
	return results, nil
}

// MockPackageFetcher for testing
type MockPackageFetcher struct {
	fetched map[string]bool
}

func NewMockPackageFetcher() *MockPackageFetcher {
	return &MockPackageFetcher{fetched: make(map[string]bool)}
}

func (m *MockPackageFetcher) FetchPackage(pkgName string, version string) error {
	m.fetched[pkgName] = true
	return nil
}

func (m *MockPackageFetcher) WasFetched(pkgName string) bool {
	return m.fetched[pkgName]
}

func TestDependencyResolver_CheckDependencies(t *testing.T) {
	t.Parallel()

	installed := map[string]*PackageInfo{
		"glibc": {
			Name:         "glibc",
			Version:      "2.31",
			Dependencies: nil,
		},
		"zlib": {
			Name:         "zlib",
			Version:      "1.2.11",
			Dependencies: []string{"glibc"},
		},
	}

	repo := NewMockPackageRepository(installed)
	fetcher := NewMockPackageFetcher()
	resolver := NewDependencyResolver(repo, fetcher)

	cases := []struct {
		name string
		pkg  *PackageInfo
		want bool
	}{
		{
			name: "all dependencies satisfied",
			pkg: &PackageInfo{
				Name:         "testpkg",
				Version:      "1.0",
				Dependencies: []string{"glibc", "zlib"},
			},
			want: true,
		},
		{
			name: "missing dependency",
			pkg: &PackageInfo{
				Name:         "testpkg2",
				Version:      "1.0",
				Dependencies: []string{"nonexistent"},
			},
			want: false,
		},
		{
			name: "version constraint not satisfied",
			pkg: &PackageInfo{
				Name:         "testpkg3",
				Version:      "1.0",
				Dependencies: []string{"glibc>=2.40"},
			},
			want: false,
		},
		{
			name: "alternative dependencies",
			pkg: &PackageInfo{
				Name:         "testpkg4",
				Version:      "1.0",
				Dependencies: []string{"lua5.1 | luajit"},
			},
			want: false, // neither alternative is installed
		},
	}

	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			err := resolver.CheckDependencies(tc.pkg)
			got := err == nil
			if got != tc.want {
				t.Errorf("CheckDependencies() = %v, want %v", got, tc.want)
				if err != nil {
					t.Logf("Error: %v", err)
				}
			}
		})
	}
}

func TestDependencyResolver_ResolveOrder(t *testing.T) {
	t.Parallel()

	packages := []*PackageInfo{
		{
			Name:         "a",
			Version:      "1.0",
			Dependencies: []string{"b"},
		},
		{
			Name:         "b",
			Version:      "1.0",
			Dependencies: []string{"c"},
		},
		{
			Name:         "c",
			Version:      "1.0",
			Dependencies: nil,
		},
	}

	installed := map[string]*PackageInfo{}
	repo := NewMockPackageRepository(installed)
	fetcher := NewMockPackageFetcher()
	resolver := NewDependencyResolver(repo, fetcher)

	order, err := resolver.ResolveOrder(packages)
	if err != nil {
		t.Fatalf("ResolveOrder() error = %v", err)
	}

	// Check that dependencies come before dependents
	pos := make(map[string]int)
	for i, pkg := range order {
		pos[pkg.Name] = i
	}

	if pos["c"] > pos["b"] || pos["b"] > pos["a"] {
		t.Errorf("Dependencies not in correct order: got %v", order)
	}
}

func TestDependencyResolver_FindMissingDependencies(t *testing.T) {
	t.Parallel()

	repo := NewMockPackageRepository(map[string]*PackageInfo{})
	fetcher := NewMockPackageFetcher()
	resolver := NewDependencyResolver(repo, fetcher)

	pkg := &PackageInfo{
		Name:         "testpkg",
		Version:      "1.0",
		Dependencies: []string{"pkg1", "pkg3", "pkg4>=1.5"},
	}

	missing := resolver.findMissingDependencies(pkg, map[string]*PackageInfo{})

	expected := []string{"pkg1", "pkg3", "pkg4>=1.5"}
	if len(missing) != len(expected) {
		t.Fatalf("Expected %d missing dependencies, got %d", len(expected), len(missing))
	}

	for i, dep := range expected {
		if i >= len(missing) || missing[i] != dep {
			t.Errorf("Expected missing dependency %s, got %s", dep, missing[i])
		}
	}
}
