package handlers

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

	"github.com/gofiber/fiber/v2"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"

	"wucher/internal/domain/base"
	filemanager "wucher/internal/domain/file_manager"
	"wucher/internal/domain/flight"
	flightinspection "wucher/internal/domain/flight_inspection"
	helicopterdomain "wucher/internal/domain/helicopter"
	helicopterfile "wucher/internal/domain/helicopter_file"
	reserveac "wucher/internal/domain/reserve_ac"
	takeoverdomain "wucher/internal/domain/takeover"
	mysqlrepo "wucher/internal/repository/mysql"
	"wucher/internal/service"
	"wucher/internal/shared/pkg/userctx"
	"wucher/internal/shared/pkg/uuidv7"
	responsedto "wucher/internal/transport/http/dto/response"
	shareddto "wucher/internal/transport/http/dto/shared"
)

type takeoverTestUserNameGetter struct {
	names map[string]string
}

func (g takeoverTestUserNameGetter) GetLastName(_ context.Context, userID []byte) (string, error) {
	return "", nil
}

func (g takeoverTestUserNameGetter) GetShortName(_ context.Context, userID []byte) (string, error) {
	if len(userID) == 0 {
		return "", nil
	}
	if name, ok := g.names[idString(userID)]; ok {
		return name, nil
	}
	return "", nil
}

func TestTakeoverHandler_buildTakeoverResponseAttributes_MapsReserveAcRelations(t *testing.T) {
	h := &TakeoverHandler{}
	takeoverID := uuidv7.MustBytes()
	baseID := uuidv7.MustBytes()
	flightID := uuidv7.MustBytes()
	reserveID := uuidv7.MustBytes()
	inspectionID := uuidv7.MustBytes()
	helicopterID := uuidv7.MustBytes()
	notes := " take-over notes "
	inspectionSummary := &shareddto.ReserveAcInspectionSummary{ID: idString(inspectionID), InspectionDate: "2026-04-20"}
	dutyResource := responsedto.DutyRosterResource{
		Attributes: responsedto.DutyRosterAttributes{
			ShiftTime: responsedto.DutyRosterShiftTime{ShiftStart: "06:00", ShiftEnd: "14:00"},
			DutyDate:  "2026-04-20",
			Detail: responsedto.DutyRosterDetailsResponse{
				Pilot: []responsedto.DutyRosterPilotCrew{{ID: idString(uuidv7.MustBytes()), Role: "pilot"}},
			},
		},
	}
	takeoverRow := &takeoverdomain.TakeoverAc{ID: takeoverID, BaseID: baseID, Notes: &notes}
	createdBy := uuidv7.MustBytes()
	updatedBy := uuidv7.MustBytes()
	takeoverRow.CreatedBy = createdBy
	takeoverRow.UpdatedBy = updatedBy
	takeoverRow.CreatedAt = time.Date(2026, 3, 12, 3, 4, 5, 0, time.UTC)
	takeoverRow.UpdatedAt = time.Date(2026, 7, 6, 8, 12, 18, 0, time.UTC)
	flightRow := &flight.Flight{ID: flightID}
	reserveRow := &reserveac.ReserveAc{
		ID:           reserveID,
		InspectionID: inspectionID,
		AircraftID:   helicopterID,
		Aircraft:     &helicopterdomain.Helicopter{ID: helicopterID, Designation: "H145", Identifier: "D-HAAA", Type: "H145"},
	}

	attrs := h.buildTakeoverResponseAttributes(nil, takeoverRow, flightRow, reserveRow, inspectionSummary, dutyResource)

	if got, want := attrs.ID, idString(takeoverID); got != want {
		t.Fatalf("unexpected takeover id: got %s want %s", got, want)
	}
	if got, want := attrs.FlightID, idString(flightID); got != want {
		t.Fatalf("unexpected flight id: got %s want %s", got, want)
	}
	if got, want := attrs.ReserveAcID, idString(reserveID); got != want {
		t.Fatalf("unexpected reserve_ac_id: got %s want %s", got, want)
	}
	if got, want := attrs.FlightInspectionID, idString(inspectionID); got != want {
		t.Fatalf("unexpected flight_inspection_id: got %s want %s", got, want)
	}
	if got, want := attrs.HelicopterID, idString(helicopterID); got != want {
		t.Fatalf("unexpected helicopter_id: got %s want %s", got, want)
	}
	if attrs.Notes == nil || *attrs.Notes != notes {
		t.Fatalf("unexpected notes: %#v", attrs.Notes)
	}
	if attrs.Inspection.ID != idString(inspectionID) {
		t.Fatalf("unexpected inspection summary: %#v", attrs.Inspection)
	}
	if attrs.Base == nil || attrs.Base.ID != idString(baseID) {
		t.Fatalf("unexpected base: %#v", attrs.Base)
	}
	if attrs.Base.ShiftTime.ShiftStart != "06:00" || attrs.Base.ShiftTime.ShiftEnd != "14:00" {
		t.Fatalf("unexpected base shift time: %#v", attrs.Base.ShiftTime)
	}
	if attrs.CreatedBy != uuidStringOrEmpty(createdBy) {
		t.Fatalf("unexpected created_by: %q", attrs.CreatedBy)
	}
	if attrs.UpdatedBy != uuidStringOrEmpty(updatedBy) {
		t.Fatalf("unexpected updated_by: %q", attrs.UpdatedBy)
	}
	if attrs.CreatedAt != takeoverRow.CreatedAt.UTC().Format(time.RFC3339) {
		t.Fatalf("unexpected created_at: %q", attrs.CreatedAt)
	}
	if attrs.UpdatedAt != takeoverRow.UpdatedAt.UTC().Format(time.RFC3339) {
		t.Fatalf("unexpected updated_at: %q", attrs.UpdatedAt)
	}
}

func TestFlightTakeoverStep_UsesUpdatedByShortName(t *testing.T) {
	prev := userctx.GlobalUserNameGetter
	updatedBy := uuidv7.MustBytes()
	createdBy := uuidv7.MustBytes()
	userctx.WireUserNameGetter(takeoverTestUserNameGetter{
		names: map[string]string{
			idString(updatedBy): "JON",
			idString(createdBy): "GAN",
		},
	})
	t.Cleanup(func() {
		userctx.WireUserNameGetter(prev)
	})

	pilotID := uuidv7.MustBytes()
	takeoverID := uuidv7.MustBytes()
	baseID := uuidv7.MustBytes()
	reserveID := uuidv7.MustBytes()
	inspectionID := uuidv7.MustBytes()
	helicopterID := uuidv7.MustBytes()
	row := &flight.Flight{
		ID:           uuidv7.MustBytes(),
		TakeoverAcID: takeoverID,
		CreatedAt:    time.Date(2026, 6, 18, 10, 0, 0, 0, time.UTC),
	}
	takeoverRow := &takeoverdomain.TakeoverAc{
		ID:        takeoverID,
		BaseID:    baseID,
		CreatedBy: createdBy,
		UpdatedBy: updatedBy,
		ReserveAc: &reserveac.ReserveAc{
			ID:           reserveID,
			AircraftID:   helicopterID,
			InspectionID: inspectionID,
			Aircraft:     &helicopterdomain.Helicopter{ID: helicopterID, Designation: "H145", Identifier: "D-HAAA", Type: "H145"},
			Inspection:   &flightinspection.FlightInspection{ID: inspectionID},
		},
		RosterCrews: []takeoverdomain.TakeoverRosterCrew{
			{
				RoleCode:  "pilot",
				UserID:    pilotID,
				NameLabel: "Pilot Full Name",
			},
		},
	}

	step := flightTakeoverStep(context.Background(), row, &takeoverServiceMock{
		getByIDFn: func(context.Context, []byte) (*takeoverdomain.TakeoverAc, error) {
			return takeoverRow, nil
		},
	})

	if step.CompletedBy != "JON" {
		t.Fatalf("expected updated_by short name, got %q", step.CompletedBy)
	}
}

func TestTakeoverHandler_buildTakeoverResponseAttributes_MapsFiles(t *testing.T) {
	h := &TakeoverHandler{}
	takeoverID := uuidv7.MustBytes()
	attachmentID := uuidv7.MustBytes()
	fileID := uuidv7.MustBytes()
	takeoverRow := &takeoverdomain.TakeoverAc{
		ID: takeoverID,
		Files: []takeoverdomain.TakeoverFile{
			{
				ID:               uuidv7.MustBytes(),
				TakeoverID:       takeoverID,
				FileAttachmentID: attachmentID,
				FileAttachment: &filemanager.Attachment{
					ID:     attachmentID,
					FileID: fileID,
					File: &filemanager.File{
						ID:        fileID,
						Name:      "takeover-report.pdf",
						ObjectKey: "files/2026/06/23/takeover-report.pdf",
						Bucket:    "bucket",
					},
				},
			},
		},
	}

	attrs := h.buildTakeoverResponseAttributes(nil, takeoverRow, &flight.Flight{ID: uuidv7.MustBytes()}, nil, nil, responsedto.DutyRosterResource{Attributes: responsedto.DutyRosterAttributes{Detail: responsedto.DutyRosterDetailsResponse{}}})

	if len(attrs.Files) != 1 {
		t.Fatalf("expected 1 takeover file, got %d", len(attrs.Files))
	}
	if attrs.Files[0].Attributes.FileAttachmentID != idString(attachmentID) {
		t.Fatalf("unexpected attachment id: %#v", attrs.Files[0])
	}
	if attrs.Files[0].Attributes.FileName != "takeover-report.pdf" {
		t.Fatalf("unexpected file name: %#v", attrs.Files[0])
	}
}

func TestTakeoverHandler_buildTakeoverResponseAttributes_HidesTemplateDerivedFiles(t *testing.T) {
	h := &TakeoverHandler{}
	takeoverID := uuidv7.MustBytes()
	regularAttID := uuidv7.MustBytes()
	regularFileID := uuidv7.MustBytes()
	templateAttID := uuidv7.MustBytes()
	templateFileID := uuidv7.MustBytes()
	takeoverRow := &takeoverdomain.TakeoverAc{
		ID: takeoverID,
		Files: []takeoverdomain.TakeoverFile{
			{ // regular attached file — must stay visible
				ID:               uuidv7.MustBytes(),
				TakeoverID:       takeoverID,
				FileAttachmentID: regularAttID,
				FileAttachment: &filemanager.Attachment{
					ID:     regularAttID,
					FileID: regularFileID,
					File: &filemanager.File{
						ID:        regularFileID,
						Name:      "evidence.pdf",
						ObjectKey: "files/evidence.pdf",
						Bucket:    "bucket",
					},
				},
			},
			{ // created from a template — must be hidden (kept in DB)
				ID:               uuidv7.MustBytes(),
				TakeoverID:       takeoverID,
				FileAttachmentID: templateAttID,
				FileAttachment: &filemanager.Attachment{
					ID:     templateAttID,
					FileID: templateFileID,
					File: &filemanager.File{
						ID:           templateFileID,
						Name:         "from-template.pdf",
						ObjectKey:    "files/from-template.pdf",
						Bucket:       "bucket",
						TemplateUUID: uuidv7.MustBytes(),
					},
				},
			},
		},
	}

	attrs := h.buildTakeoverResponseAttributes(nil, takeoverRow, &flight.Flight{ID: uuidv7.MustBytes()}, nil, nil, responsedto.DutyRosterResource{Attributes: responsedto.DutyRosterAttributes{Detail: responsedto.DutyRosterDetailsResponse{}}})

	if len(attrs.Files) != 1 {
		t.Fatalf("expected only the regular file to be visible, got %d: %#v", len(attrs.Files), attrs.Files)
	}
	if attrs.Files[0].Attributes.FileName != "evidence.pdf" {
		t.Fatalf("expected evidence.pdf visible, got %#v", attrs.Files[0])
	}
}

func TestTakeoverHandler_ChecklistBuilderHidesTemplateDerivedFiles(t *testing.T) {
	h := &TakeoverHandler{}

	visibleID := uuidv7.MustBytes()
	derivedID := uuidv7.MustBytes()
	files := []helicopterfile.HelicopterFile{
		{ID: visibleID},
		{
			ID: derivedID,
			SourceFile: &filemanager.File{
				TemplateUUID: uuidv7.MustBytes(),
			},
		},
	}

	out := h.buildChecklistSectionSummary(&fiber.Ctx{}, files, map[string]bool{idString(visibleID): true, idString(derivedID): true})
	if out.Total != 1 || out.Completed != 1 || len(out.FileChecklist) != 1 || len(out.DoneFiles) != 1 {
		t.Fatalf("unexpected filtered checklist summary: %#v", out)
	}
	if out.FileChecklist[0].HelicopterFileID != idString(visibleID) {
		t.Fatalf("expected only visible file in checklist, got %#v", out.FileChecklist[0])
	}
}

func TestTakeoverHandler_buildTakeoverResponseAttributes_LoadsFilesFromDBFallback(t *testing.T) {
	db := openTakeoverHandlerTestDB(t)
	if err := db.AutoMigrate(&filemanager.Folder{}, &filemanager.File{}, &filemanager.Attachment{}, &takeoverdomain.TakeoverAc{}, &takeoverdomain.TakeoverFile{}); err != nil {
		t.Fatalf("auto migrate: %v", err)
	}

	rootFolderID := uuidv7.MustBytes()
	if err := db.Create(&filemanager.Folder{
		ID:             rootFolderID,
		Name:           "takeovers",
		NameNormalized: "takeovers",
		Depth:          0,
		PathCache:      "/takeovers",
		CreatedAt:      time.Now().UTC(),
		UpdatedAt:      time.Now().UTC(),
	}).Error; err != nil {
		t.Fatalf("create folder: %v", err)
	}

	takeoverID := uuidv7.MustBytes()
	fileID := uuidv7.MustBytes()
	attachmentID := uuidv7.MustBytes()
	if err := db.Create(&takeoverdomain.TakeoverAc{ID: takeoverID, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC()}).Error; err != nil {
		t.Fatalf("create takeover: %v", err)
	}
	if err := db.Create(&filemanager.File{
		ID:              fileID,
		FolderID:        rootFolderID,
		Name:            "takeover-report.pdf",
		NameNormalized:  "takeover-report-pdf",
		SizeBytes:       1,
		MimeType:        "application/pdf",
		Bucket:          "bucket",
		ObjectKey:       "files/2026/06/23/takeover-report.pdf",
		ChecksumSHA256:  strings.Repeat("a", 64),
		Status:          filemanager.FileStatusReady,
		LifecycleStatus: filemanager.FileLifecycleStatusActive,
		CreatedAt:       time.Now().UTC(),
		UpdatedAt:       time.Now().UTC(),
	}).Error; err != nil {
		t.Fatalf("create file: %v", err)
	}
	if err := db.Create(&filemanager.Attachment{
		ID:        attachmentID,
		FileID:    fileID,
		RefType:   "takeover",
		RefID:     idString(takeoverID),
		CreatedAt: time.Now().UTC(),
		UpdatedAt: time.Now().UTC(),
	}).Error; err != nil {
		t.Fatalf("create attachment: %v", err)
	}
	if err := db.Create(&takeoverdomain.TakeoverFile{
		ID:               uuidv7.MustBytes(),
		TakeoverID:       takeoverID,
		FileAttachmentID: attachmentID,
	}).Error; err != nil {
		t.Fatalf("create takeover file: %v", err)
	}

	h := &TakeoverHandler{db: db}
	attrs := h.buildTakeoverResponseAttributes(nil, &takeoverdomain.TakeoverAc{ID: takeoverID}, &flight.Flight{ID: uuidv7.MustBytes()}, nil, nil, responsedto.DutyRosterResource{Attributes: responsedto.DutyRosterAttributes{Detail: responsedto.DutyRosterDetailsResponse{}}})

	if len(attrs.Files) != 1 {
		t.Fatalf("expected 1 takeover file from db fallback, got %d", len(attrs.Files))
	}
	if attrs.Files[0].Attributes.FileAttachmentID != idString(attachmentID) {
		t.Fatalf("unexpected file attachment id: %#v", attrs.Files[0])
	}
	if attrs.Files[0].Attributes.FileName != "takeover-report.pdf" {
		t.Fatalf("unexpected file name from db fallback: %#v", attrs.Files[0])
	}
}

func TestTakeoverHandler_buildTakeoverResponseAttributes_LoadsFilesFromAttachmentFallback(t *testing.T) {
	db := openTakeoverHandlerTestDB(t)
	if err := db.AutoMigrate(&filemanager.Folder{}, &filemanager.File{}, &filemanager.Attachment{}, &takeoverdomain.TakeoverAc{}); err != nil {
		t.Fatalf("auto migrate: %v", err)
	}

	rootFolderID := uuidv7.MustBytes()
	if err := db.Create(&filemanager.Folder{
		ID:             rootFolderID,
		Name:           "takeovers",
		NameNormalized: "takeovers",
		Depth:          0,
		PathCache:      "/takeovers",
		CreatedAt:      time.Now().UTC(),
		UpdatedAt:      time.Now().UTC(),
	}).Error; err != nil {
		t.Fatalf("create folder: %v", err)
	}

	takeoverID := uuidv7.MustBytes()
	fileID := uuidv7.MustBytes()
	attachmentID := uuidv7.MustBytes()
	if err := db.Create(&takeoverdomain.TakeoverAc{ID: takeoverID, CreatedAt: time.Now().UTC(), UpdatedAt: time.Now().UTC()}).Error; err != nil {
		t.Fatalf("create takeover: %v", err)
	}
	if err := db.Create(&filemanager.File{
		ID:              fileID,
		FolderID:        rootFolderID,
		Name:            "takeover-report.pdf",
		NameNormalized:  "takeover-report-pdf",
		SizeBytes:       1,
		MimeType:        "application/pdf",
		Bucket:          "bucket",
		ObjectKey:       "files/2026/06/23/takeover-report.pdf",
		ChecksumSHA256:  strings.Repeat("b", 64),
		Status:          filemanager.FileStatusReady,
		LifecycleStatus: filemanager.FileLifecycleStatusActive,
		CreatedAt:       time.Now().UTC(),
		UpdatedAt:       time.Now().UTC(),
	}).Error; err != nil {
		t.Fatalf("create file: %v", err)
	}
	if err := db.Create(&filemanager.Attachment{
		ID:        attachmentID,
		FileID:    fileID,
		RefType:   "takeover",
		RefID:     idString(takeoverID),
		CreatedAt: time.Now().UTC(),
		UpdatedAt: time.Now().UTC(),
	}).Error; err != nil {
		t.Fatalf("create attachment: %v", err)
	}

	h := &TakeoverHandler{db: db}
	attrs := h.buildTakeoverResponseAttributes(nil, &takeoverdomain.TakeoverAc{ID: takeoverID}, &flight.Flight{ID: uuidv7.MustBytes()}, nil, nil, responsedto.DutyRosterResource{Attributes: responsedto.DutyRosterAttributes{Detail: responsedto.DutyRosterDetailsResponse{}}})

	if len(attrs.Files) != 1 {
		t.Fatalf("expected 1 takeover file from attachment fallback, got %d", len(attrs.Files))
	}
	if attrs.Files[0].Attributes.FileAttachmentID != idString(attachmentID) {
		t.Fatalf("unexpected file attachment id: %#v", attrs.Files[0])
	}
	if attrs.Files[0].Attributes.FileName != "takeover-report.pdf" {
		t.Fatalf("unexpected file name from attachment fallback: %#v", attrs.Files[0])
	}
}

func TestTakeoverEditedTemplateFileBelongsToTakeover(t *testing.T) {
	takeoverA := uuidv7.MustBytes()
	takeoverB := uuidv7.MustBytes()

	tests := []struct {
		name string
		row  *filemanager.File
		want bool
	}{
		{
			name: "allows unattached draft",
			row:  &filemanager.File{},
			want: true,
		},
		{
			name: "allows matching takeover draft",
			row:  &filemanager.File{TakeoverID: append([]byte(nil), takeoverA...)},
			want: true,
		},
		{
			name: "rejects different takeover draft",
			row:  &filemanager.File{TakeoverID: append([]byte(nil), takeoverB...)},
			want: false,
		},
		{
			name: "rejects invalid stored takeover id",
			row:  &filemanager.File{TakeoverID: []byte("bad")},
			want: false,
		},
		{
			name: "rejects nil row",
			row:  nil,
			want: false,
		},
	}

	for _, tc := range tests {
		t.Run(tc.name, func(t *testing.T) {
			if got := takeoverEditedTemplateFileBelongsToTakeover(tc.row, takeoverA); got != tc.want {
				t.Fatalf("unexpected result: got %v want %v", got, tc.want)
			}
		})
	}
}

func TestTakeoverHandler_buildTakeoverResponseAttributes_ResolvesShiftTimeFromDB(t *testing.T) {
	db := openTakeoverHandlerTestDB(t)
	now := time.Now().UTC()

	category := &base.BaseCategory{
		ID:        uuidv7.MustBytes(),
		Key:       base.CategoryKeyHEMS,
		Name:      "HEMS",
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := db.Create(category).Error; err != nil {
		t.Fatalf("create category: %v", err)
	}
	repo := mysqlrepo.NewBaseRepository(db)
	baseID := uuidv7.MustBytes()
	row := &base.Base{
		ID:                   baseID,
		BaseCategoryID:       category.ID,
		BaseName:             "Lude",
		BaseAbbreviation:     "LOIG",
		DefaultStartTimeType: base.ShiftTimeTypeBMCT,
		DefaultEndTimeType:   base.ShiftTimeTypeECET,
		DefaultShiftStart:    "06:00:00",
		DefaultShiftEnd:      "21:00:00",
		OperationalShiftTimes: []base.BaseOperationalShiftTime{
			{
				DateStart:  func() *time.Time { t := time.Date(2026, 6, 10, 0, 0, 0, 0, time.UTC); return &t }(),
				DateEnd:    func() *time.Time { t := time.Date(2026, 6, 10, 0, 0, 0, 0, time.UTC); return &t }(),
				ShiftStart: "09:00:00",
				ShiftEnd:   "18:00:00",
			},
		},
		CreatedAt: now,
		UpdatedAt: now,
	}
	if err := repo.CreateBase(context.Background(), row, base.CategoryKeyHEMS); err != nil {
		t.Fatalf("create base: %v", err)
	}

	h := &TakeoverHandler{
		db:   db,
		base: service.NewBaseService(repo),
	}
	flightRow := &flight.Flight{
		ID:   uuidv7.MustBytes(),
		Date: time.Date(2026, 6, 12, 0, 0, 0, 0, time.UTC),
	}
	takeoverRow := &takeoverdomain.TakeoverAc{
		ID:     uuidv7.MustBytes(),
		BaseID: baseID,
		Base: &base.Base{
			ID:               baseID,
			BaseName:         "Lude",
			BaseAbbreviation: "LOIG",
		},
	}
	dutyResource := responsedto.DutyRosterResource{
		Attributes: responsedto.DutyRosterAttributes{
			DutyDate: "2026-06-12",
			Detail:   responsedto.DutyRosterDetailsResponse{},
		},
	}

	attrs := h.buildTakeoverResponseAttributes(nil, takeoverRow, flightRow, nil, nil, dutyResource)
	resolved := h.resolveTakeoverShiftTime(nil, baseID, flightRow.Date)
	if resolved.ShiftStart == "" || resolved.ShiftEnd == "" {
		t.Fatalf("expected resolved shift time from takeover handler")
	}
	wantStart, wantEnd, err := service.ResolveBaseShiftRangeTx(db, baseID, flightRow.Date)
	if err != nil {
		t.Fatalf("resolve shift window: %v", err)
	}

	if attrs.Base == nil {
		t.Fatalf("expected base in takeover response")
	}
	if attrs.Base.ShiftTime.ShiftStart != wantStart || attrs.Base.ShiftTime.ShiftEnd != wantEnd {
		t.Fatalf("unexpected base shift time: got=%#v want=%s %s", attrs.Base.ShiftTime, wantStart, wantEnd)
	}
	if attrs.ShiftStart != wantStart || attrs.ShiftEnd != wantEnd {
		t.Fatalf("unexpected top-level shift time: %#v %#v", attrs.ShiftStart, attrs.ShiftEnd)
	}
}

func TestBuildDutyRosterResourceFromTakeoverRows_PreservesCalendarDutyDate(t *testing.T) {
	jakarta := time.FixedZone("UTC+7", 7*60*60)
	flightRow := &flight.Flight{
		ID:           uuidv7.MustBytes(),
		TakeoverAcID: uuidv7.MustBytes(),
		Date:         time.Date(2026, time.June, 1, 0, 0, 0, 0, jakarta),
	}

	resource := buildDutyRosterResourceFromTakeoverRows(
		responsedto.DutyRosterResource{
			Attributes: responsedto.DutyRosterAttributes{
				Detail: responsedto.DutyRosterDetailsResponse{},
			},
		},
		nil,
		flightRow,
	)

	if got, want := resource.Attributes.DutyDate, "2026-06-01"; got != want {
		t.Fatalf("unexpected duty date: got %s want %s", got, want)
	}
}

func TestCalendarDateString_PreservesCalendarDate(t *testing.T) {
	jakarta := time.FixedZone("UTC+7", 7*60*60)
	value := time.Date(2026, time.June, 1, 0, 0, 0, 0, jakarta)

	if got, want := calendarDateString(value), "2026-06-01"; got != want {
		t.Fatalf("unexpected calendar date string: got %s want %s", got, want)
	}
}

func TestTakeoverHandler_resolveTakeoverReserveRow_PrefersPreloadedReserveAc(t *testing.T) {
	h := &TakeoverHandler{}
	expected := &reserveac.ReserveAc{ID: uuidv7.MustBytes(), InspectionID: uuidv7.MustBytes()}
	takeoverRow := &takeoverdomain.TakeoverAc{ReserveAc: expected}
	flightRow := &flight.Flight{ReserveAcID: uuidv7.MustBytes()}

	got := h.resolveTakeoverReserveRow(nil, takeoverRow, flightRow)
	if got != expected {
		t.Fatalf("expected preloaded reserve_ac to win, got %#v", got)
	}
}

func openTakeoverHandlerTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	dsn := fmt.Sprintf("file:takeover_handler_test_%d?mode=memory&cache=shared", time.Now().UnixNano())
	db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{NowFunc: func() time.Time { return time.Now().UTC() }})
	if err != nil {
		t.Fatalf("open sqlite: %v", err)
	}
	if err := db.AutoMigrate(&base.BaseCategory{}, &base.Base{}, &base.BaseOperationalShiftTime{}, &base.BaseContactRole{}); err != nil {
		t.Fatalf("auto migrate: %v", err)
	}
	return db
}
