package poster

import (
	"context"
	"path/filepath"
	"sync"
	"testing"
	"time"

	"pete/internal/config"
	"pete/internal/storage"
)

// mockMatrix implements the subset of matrix.Client that Queue needs.
// We test via the public Enqueue/Start interface and check storage state.

func setupTestEnv(t *testing.T) {
	t.Helper()
	dbPath := filepath.Join(t.TempDir(), "test.db")
	if err := storage.Init(dbPath); err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { storage.Close() })
}

func TestQueue_EnqueueAndDrain(t *testing.T) {
	setupTestEnv(t)

	q := &Queue{
		queues: make(map[string][]QueueItem),
		config: config.PostingConfig{
			MinIntervalSeconds:    0,
			BurstCapCount:         10,
			BurstCapWindowSeconds: 1800,
		},
		done: make(chan struct{}),
	}

	// Enqueue directly (bypass ChannelRoomID check)
	q.mu.Lock()
	q.queues["tech"] = append(q.queues["tech"], QueueItem{
		GUID:    "g1",
		Channel: "tech",
	})
	q.mu.Unlock()

	// Verify item is in queue
	q.mu.Lock()
	if len(q.queues["tech"]) != 1 {
		t.Fatalf("expected 1 item in queue, got %d", len(q.queues["tech"]))
	}
	q.mu.Unlock()
}

func TestQueueItem_MaxRetries(t *testing.T) {
	// Verify the retry counter works
	item := QueueItem{GUID: "g1", Channel: "tech", retries: 0}
	item.retries++
	if item.retries != 1 {
		t.Errorf("expected retries=1, got %d", item.retries)
	}
	item.retries++
	item.retries++
	if item.retries < maxRetries {
		t.Errorf("expected retries >= maxRetries after 3 increments, got %d", item.retries)
	}
}

func TestQueue_StartAndWait(t *testing.T) {
	setupTestEnv(t)

	q := &Queue{
		queues: make(map[string][]QueueItem),
		config: config.PostingConfig{
			MinIntervalSeconds:    0,
			BurstCapCount:         10,
			BurstCapWindowSeconds: 1800,
		},
		done: make(chan struct{}),
	}

	ctx, cancel := context.WithCancel(context.Background())

	var wg sync.WaitGroup
	wg.Add(1)
	go func() {
		defer wg.Done()
		q.Start(ctx)
	}()

	// Cancel immediately — Start should return promptly
	cancel()

	done := make(chan struct{})
	go func() {
		q.Wait()
		close(done)
	}()

	select {
	case <-done:
		// good
	case <-time.After(5 * time.Second):
		t.Fatal("queue.Wait() did not return after cancel")
	}
}

func TestQueue_BurstCap(t *testing.T) {
	setupTestEnv(t)

	cfg := config.PostingConfig{
		MinIntervalSeconds:    0,
		BurstCapCount:         2,
		BurstCapWindowSeconds: 3600,
	}

	q := &Queue{
		queues: make(map[string][]QueueItem),
		config: cfg,
		done:   make(chan struct{}),
	}

	// Simulate 2 posts already in window
	now := time.Now().Unix()
	storage.Get().Exec(`INSERT INTO post_log (guid, channel, event_id, posted_at) VALUES (?, ?, ?, ?)`,
		"prev1", "tech", "$e1", now-100)
	storage.Get().Exec(`INSERT INTO post_log (guid, channel, event_id, posted_at) VALUES (?, ?, ?, ?)`,
		"prev2", "tech", "$e2", now-50)

	// Queue a new item
	q.mu.Lock()
	q.queues["tech"] = []QueueItem{{GUID: "g3", Channel: "tech"}}
	q.mu.Unlock()

	// drainChannel should not post (burst cap reached)
	q.drainChannel("tech")

	q.mu.Lock()
	remaining := len(q.queues["tech"])
	q.mu.Unlock()

	if remaining != 1 {
		t.Errorf("expected item to remain in queue (burst cap), got %d remaining", remaining)
	}
}
