// Copyright (C) 2025 wangyusong
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

package heap

import (
	"testing"

	. "github.com/onsi/gomega"

	"github.com/glidea/zenfeed/pkg/test"
)

func TestNew(t *testing.T) {
	RegisterTestingT(t)

	type givenDetail struct{}
	type whenDetail struct {
		data []int
		less func(a, b int) bool
	}
	type thenExpected struct {
		len int
		top int
	}

	tests := []test.Case[givenDetail, whenDetail, thenExpected]{
		{
			Scenario: "Create min heap",
			When:     "creating a new min heap with initial data",
			Then:     "should create a valid heap with elements in min-heap order",
			WhenDetail: whenDetail{
				data: []int{3, 1, 4, 2},
				less: func(a, b int) bool { return a < b },
			},
			ThenExpected: thenExpected{
				len: 4,
				top: 1,
			},
		},
		{
			Scenario: "Create max heap",
			When:     "creating a new max heap with initial data",
			Then:     "should create a valid heap with elements in max-heap order",
			WhenDetail: whenDetail{
				data: []int{3, 1, 4, 2},
				less: func(a, b int) bool { return a > b },
			},
			ThenExpected: thenExpected{
				len: 4,
				top: 4,
			},
		},
		{
			Scenario: "Create empty heap",
			When:     "creating a new heap with no initial data",
			Then:     "should create an empty heap",
			WhenDetail: whenDetail{
				data: []int{},
				less: func(a, b int) bool { return a < b },
			},
			ThenExpected: thenExpected{
				len: 0,
				top: 0,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.Scenario, func(t *testing.T) {
			// When.
			h := New(tt.WhenDetail.data, tt.WhenDetail.less)

			// Then.
			Expect(h.Len()).To(Equal(tt.ThenExpected.len))
			Expect(h.Peek()).To(Equal(tt.ThenExpected.top))
		})
	}
}

func TestPushPop(t *testing.T) {
	RegisterTestingT(t)

	type givenDetail struct {
		heap *Heap[int]
	}
	type whenDetail struct {
		pushValue int
	}
	type thenExpected struct {
		popValue int
		newLen   int
		newTop   int
	}

	tests := []test.Case[givenDetail, whenDetail, thenExpected]{
		{
			Scenario: "Push and pop from min heap",
			Given:    "a min heap with initial values",
			When:     "pushing a new value and then popping",
			Then:     "should maintain heap property and return the minimum value",
			GivenDetail: givenDetail{
				heap: New([]int{3, 5, 7}, func(a, b int) bool { return a < b }),
			},
			WhenDetail: whenDetail{
				pushValue: 2,
			},
			ThenExpected: thenExpected{
				popValue: 2,
				newLen:   3,
				newTop:   3,
			},
		},
		{
			Scenario: "Push and pop from max heap",
			Given:    "a max heap with initial values",
			When:     "pushing a new value and then popping",
			Then:     "should maintain heap property and return the maximum value",
			GivenDetail: givenDetail{
				heap: New([]int{5, 3, 1}, func(a, b int) bool { return a > b }),
			},
			WhenDetail: whenDetail{
				pushValue: 8,
			},
			ThenExpected: thenExpected{
				popValue: 8,
				newLen:   3,
				newTop:   5,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.Scenario, func(t *testing.T) {
			// Given.
			h := tt.GivenDetail.heap

			// When.
			h.Push(tt.WhenDetail.pushValue)
			popValue := h.Pop()

			// Then.
			Expect(popValue).To(Equal(tt.ThenExpected.popValue))
			Expect(h.Len()).To(Equal(tt.ThenExpected.newLen))
			Expect(h.Peek()).To(Equal(tt.ThenExpected.newTop))
		})
	}
}

func TestPeek(t *testing.T) {
	RegisterTestingT(t)

	type givenDetail struct {
		heap *Heap[int]
	}
	type whenDetail struct{}
	type thenExpected struct {
		peekValue int
		unchanged bool
	}

	tests := []test.Case[givenDetail, whenDetail, thenExpected]{
		{
			Scenario: "Peek from min heap",
			Given:    "a min heap with values",
			When:     "peeking at the top element",
			Then:     "should return the minimum value without modifying the heap",
			GivenDetail: givenDetail{
				heap: New([]int{2, 4, 6}, func(a, b int) bool { return a < b }),
			},
			ThenExpected: thenExpected{
				peekValue: 2,
				unchanged: true,
			},
		},
		{
			Scenario: "Peek from max heap",
			Given:    "a max heap with values",
			When:     "peeking at the top element",
			Then:     "should return the maximum value without modifying the heap",
			GivenDetail: givenDetail{
				heap: New([]int{6, 4, 2}, func(a, b int) bool { return a > b }),
			},
			ThenExpected: thenExpected{
				peekValue: 6,
				unchanged: true,
			},
		},
		{
			Scenario: "Peek from empty heap",
			Given:    "an empty heap",
			When:     "peeking at the top element",
			Then:     "should return zero value",
			GivenDetail: givenDetail{
				heap: New([]int{}, func(a, b int) bool { return a < b }),
			},
			ThenExpected: thenExpected{
				peekValue: 0,
				unchanged: true,
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.Scenario, func(t *testing.T) {
			// Given.
			h := tt.GivenDetail.heap
			originalLen := h.Len()
			originalSlice := make([]int, len(h.Slice()))
			copy(originalSlice, h.Slice())

			// When.
			peekValue := h.Peek()

			// Then.
			Expect(peekValue).To(Equal(tt.ThenExpected.peekValue))
			if tt.ThenExpected.unchanged {
				Expect(h.Len()).To(Equal(originalLen))
				Expect(h.Slice()).To(Equal(originalSlice))
			}
		})
	}
}

func TestHeapOperations(t *testing.T) {
	RegisterTestingT(t)

	type givenDetail struct {
		initialData []int
		less        func(a, b int) bool
	}
	type whenDetail struct {
		operations []string
		pushValues []int
	}
	type thenExpected struct {
		finalLen    int
		popResults  []int
		finalValues []int
	}

	tests := []test.Case[givenDetail, whenDetail, thenExpected]{
		{
			Scenario: "Multiple operations on min heap",
			Given:    "a min heap with initial values",
			When:     "performing a series of push and pop operations",
			Then:     "should maintain heap property and return values in ascending order",
			GivenDetail: givenDetail{
				initialData: []int{5, 3, 7},
				less:        func(a, b int) bool { return a < b },
			},
			WhenDetail: whenDetail{
				operations: []string{"push", "push", "pop", "pop", "pop", "pop"},
				pushValues: []int{1, 9},
			},
			ThenExpected: thenExpected{
				finalLen:    1,
				popResults:  []int{1, 3, 5, 7},
				finalValues: []int{9},
			},
		},
		{
			Scenario: "Multiple operations on max heap",
			Given:    "a max heap with initial values",
			When:     "performing a series of push and pop operations",
			Then:     "should maintain heap property and return values in descending order",
			GivenDetail: givenDetail{
				initialData: []int{5, 3, 7},
				less:        func(a, b int) bool { return a > b },
			},
			WhenDetail: whenDetail{
				operations: []string{"push", "push", "pop", "pop", "pop", "pop"},
				pushValues: []int{1, 9},
			},
			ThenExpected: thenExpected{
				finalLen:    1,
				popResults:  []int{9, 7, 5, 3},
				finalValues: []int{1},
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.Scenario, func(t *testing.T) {
			// Given.
			h := New(tt.GivenDetail.initialData, tt.GivenDetail.less)

			// When.
			pushIndex := 0
			popResults := []int{}

			for _, op := range tt.WhenDetail.operations {
				switch op {
				case "push":
					if pushIndex < len(tt.WhenDetail.pushValues) {
						h.Push(tt.WhenDetail.pushValues[pushIndex])
						pushIndex++
					}
				case "pop":
					if h.Len() > 0 {
						popResults = append(popResults, h.Pop())
					}
				}
			}

			// Then.
			Expect(h.Len()).To(Equal(tt.ThenExpected.finalLen))
			Expect(popResults).To(Equal(tt.ThenExpected.popResults))
			Expect(h.Slice()).To(Equal(tt.ThenExpected.finalValues))
		})
	}
}
