package domain

import "testing"

// TestTransitionMatrixExhaustive enumerates every (from, to) pair and pins
// the §4.4 machine exactly: the allowed set below is the complete truth.
func TestTransitionMatrixExhaustive(t *testing.T) {
	allowed := map[TaskStatus]map[TaskStatus]bool{
		StatusImported:         {StatusAtomizing: true, StatusArchived: true},
		StatusAtomizing:        {StatusAtomized: true, StatusImported: true, StatusArchived: true},
		StatusAtomized:         {StatusPublished: true, StatusAtomizing: true, StatusArchived: true},
		StatusPublished:        {StatusClaimRequested: true, StatusAssigned: true, StatusArchived: true},
		StatusClaimRequested:   {StatusAssigned: true, StatusPublished: true, StatusArchived: true},
		StatusAssigned:         {StatusInProgress: true, StatusInReview: true, StatusPublished: true, StatusArchived: true},
		StatusInProgress:       {StatusInReview: true, StatusPublished: true, StatusArchived: true},
		StatusInReview:         {StatusApproved: true, StatusChangesRequested: true, StatusAssigned: true, StatusArchived: true},
		StatusChangesRequested: {StatusInProgress: true, StatusArchived: true},
		StatusApproved:         {StatusArchived: true},
		StatusArchived:         {},
	}

	for _, from := range AllStatuses {
		for _, to := range AllStatuses {
			want := allowed[from][to]
			if got := CanTransition(from, to); got != want {
				t.Errorf("CanTransition(%s, %s) = %v, want %v", from, to, got, want)
			}
			err := Transition(from, to)
			if want && err != nil {
				t.Errorf("Transition(%s, %s) unexpected error %v", from, to, err)
			}
			if !want && err == nil {
				t.Errorf("Transition(%s, %s) should error", from, to)
			}
		}
	}
}

func TestTransitionRejectsUnknownStatuses(t *testing.T) {
	if CanTransition("bogus", StatusArchived) {
		t.Error("unknown from-status must be rejected")
	}
	if CanTransition(StatusImported, "bogus") {
		t.Error("unknown to-status must be rejected")
	}
	if CanTransition(StatusImported, StatusImported) {
		t.Error("self-transition must be rejected")
	}
}

func TestStatusValid(t *testing.T) {
	for _, s := range AllStatuses {
		if !s.Valid() {
			t.Errorf("%s should be valid", s)
		}
	}
	if TaskStatus("nope").Valid() {
		t.Error("nope should be invalid")
	}
}

func TestComputeBounty(t *testing.T) {
	tests := []struct {
		coeff, budget, want float64
	}{
		{0.25, 1000, 250},
		{0.2, 1000, 200},
		{1.0 / 3.0, 1000, 333.33},
		{0.333, 100, 33.3},
		{0, 1000, 0},
		{1, 0, 0},
		{0.01, 99.99, 1.0}, // 0.9999 rounds to 1.00
		{0.005, 100, 0.5},
		{2.0, 500, 1000}, // extension coefficients may exceed 1 (§5.1)
	}
	for _, tt := range tests {
		if got := ComputeBounty(tt.coeff, tt.budget); got != tt.want {
			t.Errorf("ComputeBounty(%v, %v) = %v, want %v", tt.coeff, tt.budget, got, tt.want)
		}
	}
}

func TestTaskHelpers(t *testing.T) {
	task := &Task{
		Assignee:      &Assignee{Kind: "human", UserID: "dev1"},
		ClaimRequests: []ClaimRequest{{DeveloperID: "dev2"}},
	}
	if !task.IsAssignedTo("dev1") || task.IsAssignedTo("dev2") {
		t.Error("IsAssignedTo wrong")
	}
	if !task.HasClaimFrom("dev2") || task.HasClaimFrom("dev1") {
		t.Error("HasClaimFrom wrong")
	}
	aiTask := &Task{Assignee: &Assignee{Kind: "ai", JobID: "wp_1"}}
	if aiTask.IsAssignedTo("dev1") {
		t.Error("ai assignee must not match human checks")
	}
}
