package jobqueue

import (
	"testing"
)

func TestEnqueueJobAlreadyExists(t *testing.T) {
	runner := &DummyRunnable{}
	job := NewJob[DummyResult](JobID("1"), runner)
	q := NewQueue(nil)

	_ = q.Enqueue(job)
	err := q.Enqueue(job)
	if err != ErrJobAlreadyExists {
		t.Fatalf("Expected ErrJobAlreadyExists, got %v", err)
	}
}

func TestEnqueueAndDequeue(t *testing.T) {

	runner := &DummyRunnable{}

	q := NewQueue(nil)
	job1 := NewJob[DummyResult](JobID("1"), runner)
	job1.SetPriority(PriorityHigh)
	job2 := NewJob[DummyResult](JobID("2"), runner)
	_ = q.Enqueue(job1)
	_ = q.Enqueue(job2)
	dequeuedJob, err := q.Dequeue()
	if err != nil || dequeuedJob.GetID() != JobID("1") {
		t.Fatalf("Unexpected dequeue result: jobID %s, err %v", dequeuedJob.GetID(), err)
	}
}

func TestEnqueueAndDequeue2(t *testing.T) {

	runner := &DummyRunnable{}

	q := NewQueue(nil)
	job1 := NewJob[DummyResult](JobID("1"), runner)
	job2 := NewJob[DummyResult](JobID("2"), runner)

	job2.AddDependency(JobID("1"))

	_ = q.Enqueue(job1)
	_ = q.Enqueue(job2)
	dequeuedJob, err := q.Dequeue()
	if err != nil {
		t.Fatalf("Unexpected error: %v", err)
	}

	if dequeuedJob.GetID() != JobID("1") {
		t.Fatalf("Unexpected dequeue result: jobID %s", dequeuedJob.GetID())
	}
}

func TestDependencyResolution(t *testing.T) {
	runner := &DummyRunnable{}
	q := NewQueue(nil)
	job1 := NewJob[DummyResult](JobID("1"), runner)
	job2 := NewJob[DummyResult](JobID("2"), runner)
	job3 := NewJob[DummyResult](JobID("3"), runner)

	_ = q.Enqueue(job3)
	_ = q.Enqueue(job2)
	_ = q.Enqueue(job1)

	contains := func(arr []JobID, id JobID) bool {
		for _, v := range arr {
			if v == id {
				return true
			}
		}
		return false
	}

	possibleJobIDs := []JobID{"1", "2", "3"}

	job, _ := q.Dequeue()
	if !contains(possibleJobIDs, job.GetID()) {
		t.Fatalf("Expected jobID in %v, got %s", possibleJobIDs, job.GetID())
	}

	// remove jobID from possibleJobIDs
	for i, v := range possibleJobIDs {
		if v == job.GetID() {
			possibleJobIDs = append(possibleJobIDs[:i], possibleJobIDs[i+1:]...)
		}
	}

	job, _ = q.Dequeue()
	if !contains(possibleJobIDs, job.GetID()) {
		t.Fatalf("Expected jobID in %v, got %s", possibleJobIDs, job.GetID())
	}

	// remove jobID from possibleJobIDs
	for i, v := range possibleJobIDs {
		if v == job.GetID() {
			possibleJobIDs = append(possibleJobIDs[:i], possibleJobIDs[i+1:]...)
		}
	}

	job, _ = q.Dequeue()
	if !contains(possibleJobIDs, job.GetID()) {
		t.Fatalf("Expected jobID in %v, got %s", possibleJobIDs, job.GetID())
	}

	// remove jobID from possibleJobIDs
	for i, v := range possibleJobIDs {
		if v == job.GetID() {
			possibleJobIDs = append(possibleJobIDs[:i], possibleJobIDs[i+1:]...)
		}
	}

	if len(possibleJobIDs) != 0 {
		t.Fatalf("Expected no jobIDs left in %v", possibleJobIDs)
	}

}

func TestDequeueEmptyQueue(t *testing.T) {
	q := NewQueue(nil)
	_, err := q.Dequeue()
	if err != ErrQueueEmpty {
		t.Fatalf("Expected ErrQueueEmpty, got %v", err)
	}
}

func TestProcessedJobs(t *testing.T) {
	q := NewQueue(nil)
	runner := &DummyRunnable{}
	job1 := NewJob[DummyResult](JobID("1"), runner)
	job2 := NewJob[DummyResult](JobID("2"), runner)

	_ = q.Enqueue(job1)
	_, _ = q.Dequeue()
	_ = q.Enqueue(job2)

	_, err := q.Dequeue()
	if err != nil {
		t.Fatalf("Dequeue failed: %v", err)
	}

	if _, exists := q.processedJobs[job1.GetID()]; !exists {
		t.Fatalf("Job 1 not marked as processed")
	}
}

func TestCyclicDependencies(t *testing.T) {
	runner := &DummyRunnable{}
	q := NewQueue(nil)
	job1 := NewJob[DummyResult](JobID("1"), runner)
	job2 := NewJob[DummyResult](JobID("2"), runner)
	job3 := NewJob[DummyResult](JobID("3"), runner)

	job1.AddDependency(JobID("2"))
	job2.AddDependency(JobID("3"))
	job3.AddDependency(JobID("1"))

	err := q.Enqueue(job1)
	if err != nil {
		t.Fatalf("Enqueue failed: %v", err)
	}

	err = q.Enqueue(job2)
	if err != nil {
		t.Fatalf("Enqueue failed: %v", err)
	}

	err = q.Enqueue(job3)
	if err == nil || err != ErrCycleDetected {
		t.Fatalf("Expected ErrCyclicDependency, got %v", err)
	}
}

func TestDuplicateDependencies(t *testing.T) {
	runner := &DummyRunnable{}
	q := NewQueue(nil)
	job1 := NewJob[DummyResult](JobID("1"), runner)
	job2 := NewJob[DummyResult](JobID("2"), runner)

	job2.AddDependency(JobID("1"))
	job2.AddDependency(JobID("1"))

	_ = q.Enqueue(job1)
	err := q.Enqueue(job2)
	if err != nil {
		t.Fatalf("Enqueue failed: %v", err)
	}

	_, err = q.Dequeue()
	if err != nil {
		t.Fatalf("Dequeue failed: %v", err)
	}

	_, err = q.Dequeue()
	if err != nil {
		t.Fatalf("Dequeue failed: %v", err)
	}

	if len(q.processedJobs) != 2 {
		t.Fatalf("Expected 2 processed jobs, got %d", len(q.processedJobs))
	}

}

func TestJobWithSelfAsDependency(t *testing.T) {
	runner := &DummyRunnable{}
	q := NewQueue(nil)
	job1 := NewJob[DummyResult](JobID("1"), runner)

	job1.AddDependency(JobID("1"))

	err := q.Enqueue(job1)
	if err == nil || err != ErrCycleDetected {
		t.Fatalf("Expected ErrCycleDetected, got %v", err)
	}
}

// Continue with other test cases...