package service

import (
	"path/filepath"
	"testing"

	"gorm.io/driver/sqlite"
	"gorm.io/gorm"

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

func newTestDB(t *testing.T) {
	t.Helper()
	dsn := filepath.Join(t.TempDir(), "test.db")
	gdb, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
	if err != nil {
		t.Fatalf("open db: %v", err)
	}
	if err := gdb.AutoMigrate(
		&models.Team{}, &models.TeamLayer{}, &models.RealizedShift{},
		&models.ShiftInterval{}, &models.Engineer{}, &models.EngineerTeam{},
	); err != nil {
		t.Fatalf("migrate: %v", err)
	}
	db.DB = gdb
}

func TestRecordHistoryAndIdempotency(t *testing.T) {
	newTestDB(t)
	cat := models.Team{Name: "t", DisplayName: "t"}
	if err := db.DB.Create(&cat).Error; err != nil {
		t.Fatal(err)
	}
	layer := models.TeamLayer{
		TeamID: cat.ID, Priority: 1,
		People:             models.StringList{"a@x.com", "b@x.com", "c@x.com"},
		RotateEverySeconds: week, OnCallAtOnce: 1, ActiveMode: "always",
		StartsAt: monday00(),
	}
	if err := db.DB.Create(&layer).Error; err != nil {
		t.Fatal(err)
	}

	now := monday00() + 3*week + 3*24*3600 // Thursday of week 3
	if err := (&ScheduleService{}).recordTeamHistoryTx(db.DB, cat.ID, now); err != nil {
		t.Fatalf("record: %v", err)
	}

	check := func(weekIdx int64, wantEmail string) {
		var r models.RealizedShift
		err := db.DB.Where("team_id = ? AND start_time = ? AND is_backup = ?",
			cat.ID, monday00()+weekIdx*week, false).First(&r).Error
		if err != nil {
			t.Fatalf("week %d: not recorded: %v", weekIdx, err)
		}
		if r.EngineerEmail != wantEmail {
			t.Fatalf("week %d: got %s want %s", weekIdx, r.EngineerEmail, wantEmail)
		}
	}
	check(0, "a@x.com")
	check(1, "b@x.com")
	check(2, "c@x.com")
	// History freezes up to `now`, not the shift boundary; the current shift
	// (week 3) is only partially recorded so new layers take effect immediately.
	var cur models.RealizedShift
	if err := db.DB.Where("team_id = ? AND start_time = ?", cat.ID, monday00()+3*week).First(&cur).Error; err != nil {
		t.Fatalf("current shift not recorded: %v", err)
	}
	if cur.EndTime != now {
		t.Fatalf("current shift end = %d want %d (now)", cur.EndTime, now)
	}

	var before int64
	db.DB.Model(&models.RealizedShift{}).Count(&before)
	if err := (&ScheduleService{}).recordTeamHistoryTx(db.DB, cat.ID, now); err != nil {
		t.Fatalf("record again: %v", err)
	}
	var after int64
	db.DB.Model(&models.RealizedShift{}).Count(&after)
	if before != after {
		t.Fatalf("not idempotent: %d -> %d", before, after)
	}
}

func TestHistoryDoesNotBlockNewOverlayLayer(t *testing.T) {
	newTestDB(t)
	cat := models.Team{Name: "t", DisplayName: "t"}
	db.DB.Create(&cat)
	aID := mkEngineer(t, "Alice", "a@x.com")
	bID := mkEngineer(t, "Bob", "b@x.com")
	nID := mkEngineer(t, "Night", "n@x.com")
	_ = aID
	_ = bID

	base := models.TeamLayer{
		TeamID: cat.ID, Priority: 1,
		People:             models.StringList{"a@x.com", "b@x.com"},
		RotateEverySeconds: week, OnCallAtOnce: 1, ActiveMode: "always",
		StartsAt: monday00(),
	}
	db.DB.Create(&base)

	// "now" is Wednesday of week 0 at 12:00, mid-shift.
	now := monday00() + 3*24*3600 + 12*3600

	// Freeze history up to now (simulates what ReplaceTeamLayers does).
	if err := (&ScheduleService{}).recordTeamHistoryTx(db.DB, cat.ID, now); err != nil {
		t.Fatal(err)
	}

	// Add a night overlay layer (priority 2, 22:00-08:00 every day).
	overlay := models.TeamLayer{
		TeamID: cat.ID, 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(),
	}
	db.DB.Create(&overlay)
	db.DB.Create(&models.EngineerTeam{TeamID: cat.ID, EngineerID: nID})

	s := &ScheduleService{}

	// Query tonight (Wed 23:00) — the overlay should be active, not blocked
	// until next Monday.
	tonight := monday00() + 3*24*3600 + 23*3600
	rows, err := s.GetEffectiveScheduleV2([]uint{cat.ID}, tonight, tonight+1, false, false)
	if err != nil {
		t.Fatal(err)
	}
	if len(rows) == 0 {
		t.Fatal("expected on-call row for tonight, got none")
	}
	if rows[0]["engineer_email"] != "n@x.com" {
		t.Fatalf("tonight should be night overlay (n@x.com), got %v", rows[0]["engineer_email"])
	}
}

func TestBackupIsPreviousShift(t *testing.T) {
	newTestDB(t)
	cat := models.Team{Name: "t", DisplayName: "t", BackupEnabled: true}
	db.DB.Create(&cat)
	layer := models.TeamLayer{
		TeamID: cat.ID, Priority: 1,
		People:             models.StringList{"a@x.com", "b@x.com", "c@x.com"},
		RotateEverySeconds: week, OnCallAtOnce: 1, ActiveMode: "always",
		StartsAt: monday00(),
	}
	db.DB.Create(&layer)

	layers := []models.TeamLayer{layer}
	// Week 2 primary = c; backup should be week 1's primary = b.
	bsegs := (&ScheduleService{}).computeBackupWindow(cat.ID, layers, monday00()+2*week, monday00()+3*week, nil)
	if len(bsegs) != 1 || len(bsegs[0].Emails) != 1 || bsegs[0].Emails[0] != "b@x.com" {
		t.Fatalf("backup of week2 should be b@x.com, got %+v", bsegs)
	}
}
