package deps

import (
	"reflect"
	"testing"
)

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

	req, err := ParseRequirement("lua5.1 (>= 5.1.5) | luajit")
	if err != nil {
		t.Fatalf("ParseRequirement() error = %v", err)
	}

	if len(req.Alternatives) != 2 {
		t.Fatalf("expected 2 alternatives, got %d", len(req.Alternatives))
	}

	first := req.Alternatives[0]
	if first.Name != "lua5.1" || first.Op != OpGreaterOrEqual || first.Version != "5.1.5" {
		t.Fatalf("unexpected first alt: %#v", first)
	}
	second := req.Alternatives[1]
	if second.Name != "luajit" || second.Op != OpAny {
		t.Fatalf("unexpected second alt: %#v", second)
	}
}

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

	invalid := []string{
		"",
		"   ",
		"bad dep",
		"foo (>> 1)",
		"foo >=",
		"foo |",
		"| foo",
	}

	for _, raw := range invalid {
		if _, err := ParseRequirement(raw); err == nil {
			t.Fatalf("expected parse error for %q", raw)
		}
	}
}

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

	cases := []struct {
		a, b string
		want int
	}{
		{"1.0", "1.0", 0},
		{"1.0", "1.0.1", -1},
		{"2.0", "1.9", 1},
		{"1.02", "1.2", 0},
		{"1.0~rc1", "1.0", -1},
		{"1:1.0-1", "1.9.9", 1},
		{"2:1.0-1", "1:9.0-9", 1},
		{"1.0-2", "1.0-10", -1},
		{"1.0+dfsg1", "1.0+dfsg2", -1},
		{"1.0a", "1.0", 1},
	}

	for _, tc := range cases {
		got := CompareVersions(tc.a, tc.b)
		if sign(got) != sign(tc.want) {
			t.Fatalf("CompareVersions(%q, %q): want sign %d, got %d", tc.a, tc.b, sign(tc.want), sign(got))
		}
	}
}

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

	cases := []struct {
		rawDep  string
		version string
		want    bool
	}{
		{"foo", "1.0", true},
		{"foo=1.0", "1.0", true},
		{"foo=1.0", "1.1", false},
		{"foo>=1.0", "1.2", true},
		{"foo>=1.0", "0.9", false},
		{"foo<2.0", "1.9", true},
		{"foo<2.0", "2.0", false},
		{"foo (<= 2.1)", "2.1", true},
	}

	for _, tc := range cases {
		req, err := ParseRequirement(tc.rawDep)
		if err != nil {
			t.Fatalf("ParseRequirement(%q) error = %v", tc.rawDep, err)
		}
		got := req.Alternatives[0].MatchesVersion(tc.version)
		if got != tc.want {
			t.Fatalf("MatchesVersion(%q, %q): want %v, got %v", tc.rawDep, tc.version, tc.want, got)
		}
	}
}

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

	input := []string{"foo", "bar>=1.2", "baz | qux"}
	got, err := ParseRequirements(input)
	if err != nil {
		t.Fatalf("ParseRequirements() error = %v", err)
	}
	if len(got) != 3 {
		t.Fatalf("expected 3 requirements, got %d", len(got))
	}

	names := []string{got[0].Alternatives[0].Name, got[1].Alternatives[0].Name, got[2].Alternatives[0].Name}
	if want := []string{"foo", "bar", "baz"}; !reflect.DeepEqual(names, want) {
		t.Fatalf("unexpected parsed names:\nwant: %#v\ngot:  %#v", want, names)
	}
}

// Additional comprehensive tests for version comparison
func TestCompareVersionsEdgeCases(t *testing.T) {
	t.Parallel()

	cases := []struct {
		a, b string
		want int
		desc string
	}{
		// Debian epoch tests
		{"1:1.0", "1.0", 1, "epoch vs no epoch"},
		{"2:1.0", "1:2.0", 1, "different epochs"},
		{"0:1.0", "1.0", 0, "zero epoch explicit vs implicit"},
		
		// Tilde precedence tests
		{"1.0~rc1", "1.0~rc2", -1, "tilde rc versions"},
		{"1.0~rc1", "1.0", -1, "tilde vs final"},
		{"1.0~alpha", "1.0~dev", -1, "tilde alpha vs dev"},
		
		// Complex version parts
		{"1.2.3-4", "1.2.3-4ubuntu1", -1, "ubuntu suffix"},
		{"1.2.3+dfsg1", "1.2.3+dfsg2", -1, "dfsg suffix"},
		{"1.2.3-1+b1", "1.2.3-1", 1, "binNMU suffix"},
		
		// Numeric vs alphanumeric
		{"1.0a", "1.0", 1, "alpha suffix"},
		{"1.0beta", "1.0", 1, "beta suffix"},
		{"1.0rc1", "1.0", 1, "rc suffix"},
		
		// Leading zeros
		{"1.02", "1.002", 0, "leading zeros"},
		{"1.010", "1.2", 1, "leading zeros comparison"},
		
		// Empty parts
		{"1.0-", "1.0", 0, "empty release"},
		{"1.0-0", "1.0", 0, "zero release"},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			got := CompareVersions(tc.a, tc.b)
			if sign(got) != sign(tc.want) {
				t.Errorf("CompareVersions(%q, %q): want sign %d, got %d", tc.a, tc.b, sign(tc.want), sign(got))
			}
		})
	}
}

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

	cases := []struct {
		raw    string
		expect Requirement
		desc   string
	}{
		{
			"pkg (>= 1.2.3) | otherpkg (= 2.0)",
			Requirement{
				Raw: "pkg (>= 1.2.3) | otherpkg (= 2.0)",
				Alternatives: []Constraint{
					{Name: "pkg", Op: OpGreaterOrEqual, Version: "1.2.3"},
					{Name: "otherpkg", Op: OpEqual, Version: "2.0"},
				},
			},
			"debian style with alternatives",
		},
		{
			"complex-name+suffix_1.0",
			Requirement{
				Raw: "complex-name+suffix_1.0",
				Alternatives: []Constraint{
					{Name: "complex-name+suffix_1.0", Op: OpAny, Version: ""},
				},
			},
			"complex package name",
		},
	}

	for _, tc := range cases {
		t.Run(tc.desc, func(t *testing.T) {
			got, err := ParseRequirement(tc.raw)
			if err != nil {
				t.Fatalf("ParseRequirement(%q) error = %v", tc.raw, err)
			}
			if !reflect.DeepEqual(got, tc.expect) {
				t.Errorf("ParseRequirement(%q):\nwant: %#v\ngot:  %#v", tc.raw, tc.expect, got)
			}
		})
	}
}

// Property-based test for version comparison transitivity
func TestCompareVersionsTransitivity(t *testing.T) {
	t.Parallel()

	versions := []string{
		"1.0", "1.0~rc1", "1.0~dev", "1.1", "2.0",
		"1:1.0", "1:0.9", "1.0-1", "1.0-2",
	}

	for i, a := range versions {
		for j, b := range versions {
			for k, c := range versions {
				if i == j || j == k || i == k {
					continue
				}
				
				ab := CompareVersions(a, b)
				bc := CompareVersions(b, c)
				ac := CompareVersions(a, c)
				
				// If a > b and b > c, then a > c
				if ab > 0 && bc > 0 && ac <= 0 {
					t.Errorf("Transitivity violation: %s > %s and %s > %s but %s <= %s", a, b, b, c, a, c)
				}
				// If a == b and b == c, then a == c
				if ab == 0 && bc == 0 && ac != 0 {
					t.Errorf("Transitivity violation: %s == %s and %s == %s but %s != %s", a, b, b, c, a, c)
				}
			}
		}
	}
}

func sign(v int) int {
	switch {
	case v < 0:
		return -1
	case v > 0:
		return 1
	default:
		return 0
	}
}
