package seq

import (
	"context"
	"fmt"
	"testing"
	"time"

	"github.com/jackc/pgx/v5"
)

// fakeQ returns a scripted counter sequence (simulates the upsert RETURNING).
type fakeQ struct {
	counters []int64
	i        int
	seen     [][]any
}

func (f *fakeQ) QueryRow(_ context.Context, _ string, args ...any) pgx.Row {
	r := row{next: f.counters[f.i], arg: args}
	f.i++
	f.seen = append(f.seen, args)
	return r
}

// args returns the doc_date argument (second arg) of the i-th QueryRow call.
func (f *fakeQ) args(i int) time.Time {
	v, ok := f.seen[i][1].(time.Time)
	if !ok {
		panic("arg is not a time.Time")
	}
	return v
}

type row struct {
	next int64
	arg  []any
}

func (r row) Scan(dest ...any) error {
	if len(dest) == 0 {
		return fmt.Errorf("no dest")
	}
	(*dest[0].(*int64)) = r.next
	return nil
}

func TestNextSequential(t *testing.T) {
	q := &fakeQ{counters: []int64{1, 2, 3}}
	date := time.Date(2026, 8, 10, 9, 0, 0, 0, time.UTC)
	for i, want := range []int64{1, 2, 3} {
		got, err := Next(context.Background(), q, "prd", date)
		if err != nil {
			t.Fatal(err)
		}
		if got != want {
			t.Errorf("call %d: got %d, want %d", i, got, want)
		}
	}
}

func TestNextPassesMonthStartForMonthlyScope(t *testing.T) {
	// For monthly documents the service passes MonthStart(t); Next must scope
	// by that month-start date so the counter resets each calendar month.
	q := &fakeQ{counters: []int64{1, 2}}
	mid := time.Date(2026, 8, 17, 9, 0, 0, 0, time.UTC)
	if _, err := Next(context.Background(), q, "so", MonthStart(mid)); err != nil {
		t.Fatal(err)
	}
	if _, err := Next(context.Background(), q, "so", MonthStart(mid)); err != nil {
		t.Fatal(err)
	}
	want := time.Date(2026, 8, 1, 0, 0, 0, 0, time.UTC)
	if got := q.args(0); !got.Equal(want) {
		t.Errorf("first call scoped to %v, want %v", got, want)
	}
	if got := q.args(1); !got.Equal(want) {
		t.Errorf("second call scoped to %v, want %v", got, want)
	}
}

func TestMonthStart(t *testing.T) {
	wib := time.FixedZone("WIB", 7*3600)
	// Same month, different days and times -> same month-start (rollover key).
	same := []time.Time{
		time.Date(2026, 8, 1, 0, 0, 0, 0, wib),
		time.Date(2026, 8, 17, 9, 30, 0, 0, wib),
		time.Date(2026, 8, 31, 23, 59, 59, 0, wib),
	}
	for i := 1; i < len(same); i++ {
		if !MonthStart(same[i]).Equal(MonthStart(same[0])) {
			t.Errorf("MonthStart(%v) = %v, want %v", same[i], MonthStart(same[i]), MonthStart(same[0]))
		}
	}
	// Rollover: the next month starts a new scope.
	if MonthStart(time.Date(2026, 9, 1, 0, 0, 0, 0, wib)).Equal(MonthStart(same[0])) {
		t.Error("September must roll over to a new monthly scope")
	}
	// Timezone boundary: a UTC timestamp that is already the next WIB month
	// must not be treated as the current month.
	utc := time.Date(2026, 8, 31, 18, 0, 0, 0, time.UTC) // 01:00 WIB Sep 1
	ms := MonthStart(utc.In(wib))
	if ms.Month() != time.September || ms.Year() != 2026 {
		t.Errorf("MonthStart across WIB boundary = %v, want Sep 2026", ms)
	}
	// Location is preserved.
	if MonthStart(same[0]).Location() != wib {
		t.Error("MonthStart must preserve the caller location")
	}
}
