// 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/>.

// TODO: fix tests
package feed

// import (
// 	"context"
// 	"os"
// 	"testing"
// 	"time"
//

// 	"github.com/benbjohnson/clock"
// 	. "github.com/onsi/gomega"
// 	"github.com/stretchr/testify/mock"

// 	"github.com/glidea/zenfeed/pkg/config"
// 	"github.com/glidea/zenfeed/pkg/storage/feed/block"
// 	"github.com/glidea/zenfeed/pkg/storage/feed/block/chunk"
// 	"github.com/glidea/zenfeed/pkg/test"
// 	timeutil "github.com/glidea/zenfeed/pkg/util/time"
// )

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

// 	type givenDetail struct {
// 		now          time.Time
// 		blocksOnDisk []string // Block directory names in format "2006-01-02T15:04:05Z-2006-01-02T15:04:05Z"
// 	}
// 	type whenDetail struct {
// 		app *config.App
// 	}
// 	type thenExpected struct {
// 		storage        storage
// 		storageHotLen  int
// 		storageColdLen int
// 		blockCalls     []func(obj *mock.Mock)
// 	}
// 	tests := []test.Case[givenDetail, whenDetail, thenExpected]{
// 		{
// 			Scenario: "Create a new storage from an empty directory",
// 			Given:    "just mock a time",
// 			When:     "call New with a config with a data directory",
// 			Then:     "should return a new storage and a hot block created",
// 			GivenDetail: givenDetail{
// 				now: timeutil.MustParse("2025-03-03T10:00:00Z"),
// 			},
// 			WhenDetail: whenDetail{
// 				app: &config.App{
// 					DB: config.DB{
// 						Dir: "/tmp/TestNew",
// 					},
// 				},
// 			},
// 			ThenExpected: thenExpected{
// 				storage: storage{
// 					config: &Config{
// 						Dir: "/tmp/TestNew",
// 					},
// 				},
// 				storageHotLen:  1,
// 				storageColdLen: 0,
// 			},
// 		},
// 		{
// 			Scenario: "Create a storage from existing directory with blocks",
// 			Given:    "existing blocks on disk",
// 			GivenDetail: givenDetail{
// 				now: timeutil.MustParse("2025-03-03T10:00:00Z"),
// 				blocksOnDisk: []string{
// 					"2025-03-02T10:00:00Z ~ 2025-03-03T10:00:00Z", // Hot block
// 					"2025-03-01T10:00:00Z ~ 2025-03-02T10:00:00Z", // Cold block
// 					"2025-02-28T10:00:00Z ~ 2025-03-01T10:00:00Z", // Cold block
// 				},
// 			},
// 			When: "call New with a config with existing data directory",
// 			WhenDetail: whenDetail{
// 				app: &config.App{
// 					DB: config.DB{
// 						Dir:             "/tmp/TestNew",
// 						WriteableWindow: 49 * time.Hour,
// 					},
// 				},
// 			},
// 			Then: "should return a storage with existing blocks loaded",
// 			ThenExpected: thenExpected{
// 				storage: storage{
// 					config: &Config{
// 						Dir: "/tmp/TestNew",
// 						Block: BlockConfig{
// 							WriteableWindow: 49 * time.Hour,
// 						},
// 					},
// 				},
// 				storageHotLen:  1,
// 				storageColdLen: 2,
// 				blockCalls: []func(obj *mock.Mock){
// 					func(m *mock.Mock) {
// 						m.On("State").Return(block.StateHot).Once()
// 					},
// 					func(m *mock.Mock) {
// 						m.On("State").Return(block.StateCold).Once()
// 					},
// 					func(m *mock.Mock) {
// 						m.On("State").Return(block.StateCold).Once()
// 					},
// 				},
// 			},
// 		},
// 	}

// 	for _, tt := range tests {
// 		t.Run(tt.Scenario, func(t *testing.T) {
// 			// Given.
// 			c := clock.NewMock()
// 			c.Set(tt.GivenDetail.now)
// 			clk = c // Set global clock.
// 			defer func() { clk = clock.New() }()

// 			// Create test directories if needed
// 			if len(tt.GivenDetail.blocksOnDisk) > 0 {
// 				for _, blockDir := range tt.GivenDetail.blocksOnDisk {
// 					err := os.MkdirAll(tt.WhenDetail.app.DB.Dir+"/"+blockDir, 0755)
// 					Expect(err).To(BeNil())
// 				}
// 			}

// 			// When.
// 			var calls int
// 			var blockCalls []*mock.Mock
// 			blockFactory := block.NewFactory(func(obj *mock.Mock) {
// 				if calls < len(tt.ThenExpected.blockCalls) {
// 					tt.ThenExpected.blockCalls[calls](obj)
// 					calls++
// 					blockCalls = append(blockCalls, obj)
// 				}
// 			})
// 			s, err := new(tt.WhenDetail.app, blockFactory)
// 			defer os.RemoveAll(tt.WhenDetail.app.DB.Dir)

// 			// Then.
// 			Expect(err).To(BeNil())
// 			Expect(s).NotTo(BeNil())
// 			storage := s.(*storage)
// 			Expect(storage.config).To(Equal(tt.ThenExpected.storage.config))
// 			Expect(len(storage.hot.blocks)).To(Equal(tt.ThenExpected.storageHotLen))
// 			Expect(len(storage.cold.blocks)).To(Equal(tt.ThenExpected.storageColdLen))
// 			for _, call := range blockCalls {
// 				call.AssertExpectations(t)
// 			}
// 		})
// 	}
// }

// func TestAppend(t *testing.T) {
// 	RegisterTestingT(t)

// 	type givenDetail struct {
// 		hotBlocks  []func(m *mock.Mock)
// 		coldBlocks []func(m *mock.Mock)
// 	}
// 	type whenDetail struct {
// 		feeds []*chunk.Feed
// 	}
// 	type thenExpected struct {
// 		err string
// 	}

// 	tests := []test.Case[givenDetail, whenDetail, thenExpected]{
// 		{
// 			Scenario: "Append feeds to hot block",
// 			Given:    "a storage with one hot block",
// 			When:     "append feeds within hot block time range",
// 			Then:     "should append feeds to hot block successfully",
// 			GivenDetail: givenDetail{
// 				hotBlocks: []func(m *mock.Mock){
// 					func(m *mock.Mock) {
// 						m.On("Start").Return(timeutil.MustParse("2025-03-02T10:00:00Z")).Twice()
// 						m.On("End").Return(timeutil.MustParse("2025-03-03T10:00:00Z")).Twice()
// 						m.On("State").Return(block.StateHot).Twice()
// 						m.On("Append", mock.Anything, []*chunk.Feed{
// 							{ID: 1, Time: timeutil.MustParse("2025-03-02T11:00:00Z")},
// 							{ID: 2, Time: timeutil.MustParse("2025-03-02T12:00:00Z")},
// 						}).Return(nil)
// 					},
// 				},
// 			},
// 			WhenDetail: whenDetail{
// 				feeds: []*chunk.Feed{
// 					{ID: 1, Time: timeutil.MustParse("2025-03-02T11:00:00Z")},
// 					{ID: 2, Time: timeutil.MustParse("2025-03-02T12:00:00Z")},
// 				},
// 			},
// 			ThenExpected: thenExpected{
// 				err: "",
// 			},
// 		},
// 		{
// 			Scenario: "Append feeds to non-hot block",
// 			Given:    "a storage with hot and cold blocks",
// 			When:     "append feeds with time in cold block range",
// 			Then:     "should return error",
// 			GivenDetail: givenDetail{
// 				coldBlocks: []func(m *mock.Mock){
// 					func(m *mock.Mock) {},
// 				},
// 			},
// 			WhenDetail: whenDetail{
// 				feeds: []*chunk.Feed{
// 					{ID: 1, Time: timeutil.MustParse("2025-03-01T11:00:00Z")},
// 				},
// 			},
// 			ThenExpected: thenExpected{
// 				err: "cannot find hot block",
// 			},
// 		},
// 	}

// 	for _, tt := range tests {
// 		t.Run(tt.Scenario, func(t *testing.T) {
// 			// Given.
// 			calls := 0
// 			var blockMocks []*mock.Mock
// 			blockFactory := block.NewFactory(func(obj *mock.Mock) {
// 				if calls < len(tt.GivenDetail.hotBlocks) {
// 					tt.GivenDetail.hotBlocks[calls](obj)
// 					calls++
// 					blockMocks = append(blockMocks, obj)
// 				}
// 			})
// 			var hotBlocks blockChain
// 			for range tt.GivenDetail.hotBlocks {
// 				block, err := blockFactory.New(nil, nil, nil, nil, nil)
// 				Expect(err).To(BeNil())
// 				hotBlocks.add(block)
// 			}
// 			blockFactory = block.NewFactory(func(obj *mock.Mock) {
// 				if calls < len(tt.GivenDetail.coldBlocks) {
// 					tt.GivenDetail.coldBlocks[calls](obj)
// 					calls++
// 					blockMocks = append(blockMocks, obj)
// 				}
// 			})
// 			var coldBlocks blockChain
// 			for range tt.GivenDetail.coldBlocks {
// 				block, err := blockFactory.New(nil, nil, nil, nil, nil)
// 				Expect(err).To(BeNil())
// 				coldBlocks.add(block)
// 			}
// 			s := storage{
// 				hot:  &hotBlocks,
// 				cold: &coldBlocks,
// 			}

// 			// When.
// 			err := s.Append(context.Background(), tt.WhenDetail.feeds...)

// 			// Then.
// 			if tt.ThenExpected.err != "" {
// 				Expect(err.Error()).To(ContainSubstring(tt.ThenExpected.err))
// 			} else {
// 				Expect(err).To(BeNil())
// 			}
// 			for _, m := range blockMocks {
// 				m.AssertExpectations(t)
// 			}
// 		})
// 	}
// }

// func TestQuery(t *testing.T) {
// 	RegisterTestingT(t)

// 	type givenDetail struct {
// 		hotBlocks  []func(m *mock.Mock)
// 		coldBlocks []func(m *mock.Mock)
// 	}
// 	type whenDetail struct {
// 		query block.QueryOptions
// 	}
// 	type thenExpected struct {
// 		feeds []*block.FeedVO
// 		err   string
// 	}

// 	tests := []test.Case[givenDetail, whenDetail, thenExpected]{
// 		{
// 			Scenario: "Query feeds from hot blocks",
// 			Given:    "a storage with one hot block containing feeds",
// 			When:     "querying with time range within hot block",
// 			Then:     "should return matching feeds from hot block",
// 			GivenDetail: givenDetail{
// 				hotBlocks: []func(m *mock.Mock){
// 					func(m *mock.Mock) {
// 						m.On("Start").Return(timeutil.MustParse("2025-03-02T10:00:00Z")).Once()
// 						m.On("End").Return(timeutil.MustParse("2025-03-03T10:00:00Z")).Once()
// 						m.On("Query", mock.Anything, mock.MatchedBy(func(q block.QueryOptions) bool {
// 							return q.Start.Equal(timeutil.MustParse("2025-03-02T12:00:00Z")) &&
// 								q.End.Equal(timeutil.MustParse("2025-03-02T14:00:00Z"))
// 						})).Return([]*block.FeedVO{
// 							{ID: 1, Time: timeutil.MustParse("2025-03-02T12:30:00Z")},
// 							{ID: 2, Time: timeutil.MustParse("2025-03-02T13:00:00Z")},
// 						}, nil)
// 					},
// 				},
// 			},
// 			WhenDetail: whenDetail{
// 				query: block.QueryOptions{
// 					Start: timeutil.MustParse("2025-03-02T12:00:00Z"),
// 					End:   timeutil.MustParse("2025-03-02T14:00:00Z"),
// 					Limit: 10,
// 				},
// 			},
// 			ThenExpected: thenExpected{
// 				feeds: []*block.FeedVO{
// 					{ID: 2, Time: timeutil.MustParse("2025-03-02T13:00:00Z")},
// 					{ID: 1, Time: timeutil.MustParse("2025-03-02T12:30:00Z")},
// 				},
// 				err: "",
// 			},
// 		},
// 		{
// 			Scenario: "Query feeds from multiple blocks",
// 			Given:    "a storage with hot and cold blocks containing feeds",
// 			When:     "querying with time range spanning multiple blocks",
// 			Then:     "should return combined and sorted feeds from all matching blocks",
// 			GivenDetail: givenDetail{
// 				hotBlocks: []func(m *mock.Mock){
// 					func(m *mock.Mock) {
// 						m.On("Start").Return(timeutil.MustParse("2025-03-02T10:00:00Z"))
// 						m.On("End").Return(timeutil.MustParse("2025-03-03T10:00:00Z"))
// 						m.On("Query", mock.Anything, mock.MatchedBy(func(q block.QueryOptions) bool {
// 							return !q.Start.IsZero() && q.End.IsZero()
// 						})).Return([]*block.FeedVO{
// 							{ID: 3, Time: timeutil.MustParse("2025-03-02T15:00:00Z")},
// 							{ID: 4, Time: timeutil.MustParse("2025-03-02T16:00:00Z")},
// 						}, nil)
// 					},
// 				},
// 				coldBlocks: []func(m *mock.Mock){
// 					func(m *mock.Mock) {
// 						m.On("Start").Return(timeutil.MustParse("2025-03-01T10:00:00Z"))
// 						m.On("End").Return(timeutil.MustParse("2025-03-02T10:00:00Z"))
// 						m.On("Query", mock.Anything, mock.MatchedBy(func(q block.QueryOptions) bool {
// 							return !q.Start.IsZero() && q.End.IsZero()
// 						})).Return([]*block.FeedVO{
// 							{ID: 1, Time: timeutil.MustParse("2025-03-01T15:00:00Z")},
// 							{ID: 2, Time: timeutil.MustParse("2025-03-01T16:00:00Z")},
// 						}, nil)
// 					},
// 				},
// 			},
// 			WhenDetail: whenDetail{
// 				query: block.QueryOptions{
// 					Start: timeutil.MustParse("2025-03-01T12:00:00Z"),
// 					Limit: 3,
// 				},
// 			},
// 			ThenExpected: thenExpected{
// 				feeds: []*block.FeedVO{
// 					{ID: 4, Time: timeutil.MustParse("2025-03-02T16:00:00Z")},
// 					{ID: 3, Time: timeutil.MustParse("2025-03-02T15:00:00Z")},
// 					{ID: 2, Time: timeutil.MustParse("2025-03-01T16:00:00Z")},
// 				},
// 				err: "",
// 			},
// 		},
// 	}

// 	for _, tt := range tests {
// 		t.Run(tt.Scenario, func(t *testing.T) {
// 			// Given.
// 			calls := 0
// 			var blockMocks []*mock.Mock
// 			blockFactory := block.NewFactory(func(obj *mock.Mock) {
// 				if calls < len(tt.GivenDetail.hotBlocks) {
// 					tt.GivenDetail.hotBlocks[calls](obj)
// 					calls++
// 					blockMocks = append(blockMocks, obj)
// 				}
// 			})
// 			var hotBlocks blockChain
// 			for range tt.GivenDetail.hotBlocks {
// 				block, err := blockFactory.New(nil, nil, nil, nil, nil)
// 				Expect(err).To(BeNil())
// 				hotBlocks.add(block)
// 			}

// 			blockFactory = block.NewFactory(func(obj *mock.Mock) {
// 				if calls < len(tt.GivenDetail.hotBlocks)+len(tt.GivenDetail.coldBlocks) {
// 					tt.GivenDetail.coldBlocks[calls-len(tt.GivenDetail.hotBlocks)](obj)
// 					calls++
// 					blockMocks = append(blockMocks, obj)
// 				}
// 			})
// 			var coldBlocks blockChain
// 			for range tt.GivenDetail.coldBlocks {
// 				block, err := blockFactory.New(nil, nil, nil, nil, nil)
// 				Expect(err).To(BeNil())
// 				coldBlocks.add(block)
// 			}

// 			s := storage{
// 				hot:  &hotBlocks,
// 				cold: &coldBlocks,
// 			}

// 			// When.
// 			feeds, err := s.Query(context.Background(), tt.WhenDetail.query)

// 			// Then.
// 			if tt.ThenExpected.err != "" {
// 				Expect(err).NotTo(BeNil())
// 				Expect(err.Error()).To(ContainSubstring(tt.ThenExpected.err))
// 			} else {
// 				Expect(err).To(BeNil())
// 				Expect(feeds).To(HaveLen(len(tt.ThenExpected.feeds)))

// 				// Check feeds match expected
// 				for i, feed := range feeds {
// 					Expect(feed.ID).To(Equal(tt.ThenExpected.feeds[i].ID))
// 					Expect(feed.Time).To(Equal(tt.ThenExpected.feeds[i].Time))
// 					Expect(feed.Labels).To(Equal(tt.ThenExpected.feeds[i].Labels))
// 				}
// 			}

// 			for _, m := range blockMocks {
// 				m.AssertExpectations(t)
// 			}
// 		})
// 	}
// }
