package service

import (
	"testing"
	"time"

	"github.com/tidbcloud/aisupport/services/oncall/models"
)

// monday00 is a known Monday 00:00 UTC+8 used as a rotation anchor in tests.
func monday00() int64 {
	// 2024-01-01 is a Monday.
	return time.Date(2024, 1, 1, 0, 0, 0, 0, fixedUTC8).Unix()
}

const week = int64(7 * 24 * 3600)

func TestLayerWeeklyRotation(t *testing.T) {
	layer := models.TeamLayer{
		People:             models.StringList{"a@x.com", "b@x.com", "c@x.com"},
		RotateEverySeconds: week,
		OnCallAtOnce:       1,
		ActiveMode:         "always",
		StartsAt:           monday00(),
	}
	cases := []struct {
		offsetWeeks int64
		want        string
	}{{0, "a@x.com"}, {1, "b@x.com"}, {2, "c@x.com"}, {3, "a@x.com"}}
	for _, c := range cases {
		got := layerOnCallAt(layer, monday00()+c.offsetWeeks*week+3600)
		if len(got) != 1 || got[0] != c.want {
			t.Fatalf("week+%d: got %v want %s", c.offsetWeeks, got, c.want)
		}
	}
}

func TestLayerConcurrency(t *testing.T) {
	layer := models.TeamLayer{
		People:             models.StringList{"a@x.com", "b@x.com", "c@x.com"},
		RotateEverySeconds: week,
		OnCallAtOnce:       2,
		ActiveMode:         "always",
		StartsAt:           monday00(),
	}
	// The group advances by OnCallAtOnce (2) each week. With 3 people the window
	// wraps, so every shift still has 2 people (the wrap overlaps the head).
	want := [][]string{
		{"a@x.com", "b@x.com"},
		{"c@x.com", "a@x.com"},
		{"b@x.com", "c@x.com"},
	}
	for i, w := range want {
		got := layerOnCallAt(layer, monday00()+int64(i)*week+3600)
		if !emailsEqual(got, w) {
			t.Fatalf("slot %d: got %v want %v", i, got, w)
		}
	}
}

// TestLayerGroupRotationEvenSplit covers the clean case: headcount divisible by
// OnCallAtOnce gives non-overlapping fixed groups that swap whole each period.
// Uses a daily period to confirm rotation is independent of the (weekly) default.
func TestLayerGroupRotationDailyEvenSplit(t *testing.T) {
	const day = int64(24 * 3600)
	layer := models.TeamLayer{
		People:             models.StringList{"a@x.com", "b@x.com", "c@x.com", "d@x.com"},
		RotateEverySeconds: day,
		OnCallAtOnce:       2,
		ActiveMode:         "always",
		StartsAt:           monday00(),
	}
	want := [][]string{
		{"a@x.com", "b@x.com"},
		{"c@x.com", "d@x.com"},
		{"a@x.com", "b@x.com"},
		{"c@x.com", "d@x.com"},
	}
	for i, w := range want {
		got := layerOnCallAt(layer, monday00()+int64(i)*day+3600)
		if !emailsEqual(got, w) {
			t.Fatalf("day %d: got %v want %v", i, got, w)
		}
	}
}

func TestLayerCrossMidnightWindow(t *testing.T) {
	// Night shift Mon-Fri 21:00 -> 05:00 (crosses midnight).
	layer := models.TeamLayer{
		People:         models.StringList{"n@x.com"},
		ActiveMode:     "window",
		ActiveDays:     models.IntList{1, 2, 3, 4, 5},
		ActiveStartMin: 21 * 60,
		ActiveEndMin:   5 * 60,
		StartsAt:       monday00(),
	}
	at := func(dayOffset, hour int) int64 {
		return monday00() + int64(dayOffset)*24*3600 + int64(hour)*3600
	}
	checks := []struct {
		name   string
		ts     int64
		active bool
	}{
		{"Mon 22:00", at(0, 22), true},
		{"Mon 12:00", at(0, 12), false},
		{"Tue 04:00 (Mon tail)", at(1, 4), true},
		{"Sat 04:00 (Fri tail)", at(5, 4), true},
		{"Sat 22:00 (Sat not active)", at(5, 22), false},
		{"Sun 22:00", at(6, 22), false},
	}
	for _, c := range checks {
		if got := layerActiveAt(layer, c.ts); got != c.active {
			t.Fatalf("%s: active=%v want %v", c.name, got, c.active)
		}
	}
}

func TestResolvePriorityAndWindow(t *testing.T) {
	// Priority order returned by LoadTeamLayers is desc; build it that way.
	night := models.TeamLayer{
		Priority:       2,
		People:         models.StringList{"night@x.com"},
		ActiveMode:     "window",
		ActiveDays:     models.IntList{0, 1, 2, 3, 4, 5, 6},
		ActiveStartMin: 22 * 60,
		ActiveEndMin:   8 * 60, // 22:00 -> 08:00
		StartsAt:       monday00(),
	}
	day := models.TeamLayer{
		Priority:           1,
		People:             models.StringList{"day@x.com"},
		RotateEverySeconds: week,
		ActiveMode:         "always",
		StartsAt:           monday00(),
	}
	layers := []models.TeamLayer{night, day} // priority desc

	at := func(hour int) int64 { return monday00() + int64(hour)*3600 }
	if got := resolveLayersAt(layers, at(2)); len(got) != 1 || got[0] != "night@x.com" {
		t.Fatalf("02:00 want night, got %v", got)
	}
	if got := resolveLayersAt(layers, at(12)); len(got) != 1 || got[0] != "day@x.com" {
		t.Fatalf("12:00 want day, got %v", got)
	}
	if got := resolveLayersAt(layers, at(23)); len(got) != 1 || got[0] != "night@x.com" {
		t.Fatalf("23:00 want night, got %v", got)
	}
}

func TestComputeWindowMergesAndSplits(t *testing.T) {
	// Day 08-22 by 'd', night 22-08 by 'n', whole week.
	day := models.TeamLayer{
		Priority: 1, People: models.StringList{"d@x.com"},
		ActiveMode: "window", ActiveDays: models.IntList{0, 1, 2, 3, 4, 5, 6},
		ActiveStartMin: 8 * 60, ActiveEndMin: 22 * 60, StartsAt: monday00(),
	}
	night := models.TeamLayer{
		Priority: 2, People: models.StringList{"n@x.com"},
		ActiveMode: "window", ActiveDays: models.IntList{0, 1, 2, 3, 4, 5, 6},
		ActiveStartMin: 22 * 60, ActiveEndMin: 8 * 60, StartsAt: monday00(),
	}
	layers := []models.TeamLayer{night, day} // priority desc

	from := monday00() + 8*3600 // Mon 08:00
	to := monday00() + 24*3600  // Tue 00:00
	segs := computeWindowFromLayers(7, layers, from, to, nil)
	// Expect: Mon 08-22 d@, Mon 22-Tue00 n@
	if len(segs) != 2 {
		t.Fatalf("want 2 segments, got %d: %+v", len(segs), segs)
	}
	if segs[0].StartTime != from || segs[0].EndTime != monday00()+22*3600 || segs[0].Emails[0] != "d@x.com" {
		t.Fatalf("seg0 wrong: %+v", segs[0])
	}
	if segs[1].StartTime != monday00()+22*3600 || segs[1].EndTime != to || segs[1].Emails[0] != "n@x.com" {
		t.Fatalf("seg1 wrong: %+v", segs[1])
	}
}
