package service

import (
	"fmt"
	"path/filepath"
	"strings"
	"testing"
	"time"

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

	"wucher/internal/domain/base"
	beforeflightinspection "wucher/internal/domain/before_flight_inspection"
	filemanager "wucher/internal/domain/file_manager"
	flightinspection "wucher/internal/domain/flight_inspection"
	flightprepcheck "wucher/internal/domain/flight_prep_check"
	helicopterfile "wucher/internal/domain/helicopter_file"
	"wucher/internal/repository/mysql"
	"wucher/internal/shared/pkg/uuidv7"
	requestdto "wucher/internal/transport/http/dto/request"
)

func openTakeoverServiceTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", filepath.Join(t.TempDir(), "takeover.db"))
	db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
	if err != nil {
		t.Fatalf("open sqlite: %v", err)
	}
	if err := db.AutoMigrate(&base.BaseCategory{}, &base.Base{}, &base.BaseOperationalShiftTime{}); err != nil {
		t.Fatalf("auto migrate base models: %v", err)
	}
	if err := db.AutoMigrate(&flightinspection.FlightInspection{}, &beforeflightinspection.BeforeFlightInspection{}, &flightprepcheck.FlightPrepCheck{}); err != nil {
		t.Fatalf("auto migrate inspection models: %v", err)
	}
	if err := db.Exec(`CREATE TABLE helicopter_files (
		id BLOB PRIMARY KEY,
		section TEXT NOT NULL,
		position INTEGER NOT NULL DEFAULT 1,
		is_mandatory INTEGER NOT NULL DEFAULT 0,
		helicopter_id BLOB NOT NULL,
		file_attachment_id BLOB,
		source_file_id BLOB,
		created_at DATETIME,
		created_by BLOB,
		updated_at DATETIME,
		updated_by BLOB
	)`).Error; err != nil {
		t.Fatalf("create helicopter_files table: %v", err)
	}
	if err := db.Exec(`CREATE TABLE flight_inspection_file_checklists (
		id BLOB PRIMARY KEY,
		is_done INTEGER NOT NULL DEFAULT 0,
		helicopter_file_id BLOB NOT NULL,
		flight_inspection_id BLOB NOT NULL,
		created_at DATETIME,
		created_by BLOB,
		updated_at DATETIME,
		updated_by BLOB,
		UNIQUE (flight_inspection_id, helicopter_file_id)
	)`).Error; err != nil {
		t.Fatalf("create flight_inspection_file_checklists table: %v", err)
	}
	return db
}

// newChecklistTestService builds a TakeoverService wired with the concrete repository so
// the checklist upsert can load template files (DB access lives in the repository layer).
func newChecklistTestService(db *gorm.DB) *TakeoverService {
	return NewTakeoverService(db).WithChecklistTemplateRepository(mysql.NewTakeoverRepository(db))
}

func TestValidateTakeoverInspectionChecklistsAllowsEmptySections(t *testing.T) {
	if err := validateTakeoverInspectionChecklists(&requestdto.ReserveAcInspectionCreateAttributes{}); err != nil {
		t.Fatalf("expected empty sections to be allowed, got %v", err)
	}
}

func TestUpsertTakeoverInspectionTxAllowsMissingFleetStatusFileOnUpdate(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	now := time.Now().UTC()
	helicopterID := uuidv7.MustBytes()
	inspectionID := uuidv7.MustBytes()
	inspectionDate := time.Date(2026, 6, 24, 0, 0, 0, 0, time.UTC)

	if err := db.Create(&flightinspection.FlightInspection{
		ID:             inspectionID,
		InspectionDate: inspectionDate,
		Status:         flightinspection.StatusDraft,
		CreatedAt:      now,
		UpdatedAt:      now,
	}).Error; err != nil {
		t.Fatalf("create inspection: %v", err)
	}

	req := &requestdto.ReserveAcInspectionCreateAttributes{
		InspectionDate: "2026-06-24",
		Before: requestdto.ReserveAcBeforeInspectionAttributes{
			OilEngineNR1Checked:       boolPtr(true),
			OilEngineNR2Checked:       boolPtr(true),
			HydraulicLHChecked:        boolPtr(true),
			HydraulicRHChecked:        boolPtr(true),
			OilTransmissionMGBChecked: boolPtr(true),
			OilTransmissionIGBChecked: boolPtr(false),
			OilTransmissionTGBChecked: boolPtr(true),
			FuelAmount:                floatPtr(1),
			FuelUnit:                  stringPtr(beforeflightinspection.FuelUnitLT),
			FuelAddedAmount:           floatPtr(0),
		},
		Prepare: requestdto.ReserveAcPrepareInspectionAttributes{
			NotamBriefing:         boolPtr(true),
			WeatherBriefing:       boolPtr(true),
			OperationalFlightPlan: boolPtr(false),
		},
	}

	gotID, err := newChecklistTestService(db).upsertTakeoverInspectionTx(db, nil, inspectionID, req, helicopterID, uuidv7.MustBytes())
	if err != nil {
		t.Fatalf("expected update inspection without fleet_status_file to pass, got %v", err)
	}
	if string(gotID) != string(inspectionID) {
		t.Fatalf("unexpected inspection id: %x", gotID)
	}
}

func TestUpsertTakeoverInspectionTxAllowsMissingFileChecklistsOnUpdate(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	now := time.Now().UTC()
	helicopterID := uuidv7.MustBytes()
	inspectionID := uuidv7.MustBytes()
	inspectionDate := time.Date(2026, 6, 24, 0, 0, 0, 0, time.UTC)

	if err := db.Create(&flightinspection.FlightInspection{
		ID:             inspectionID,
		InspectionDate: inspectionDate,
		Status:         flightinspection.StatusDraft,
		CreatedAt:      now,
		UpdatedAt:      now,
	}).Error; err != nil {
		t.Fatalf("create inspection: %v", err)
	}

	req := &requestdto.ReserveAcInspectionCreateAttributes{
		InspectionDate: "2026-06-24",
		Before: requestdto.ReserveAcBeforeInspectionAttributes{
			OilEngineNR1Checked:       boolPtr(true),
			OilEngineNR2Checked:       boolPtr(true),
			HydraulicLHChecked:        boolPtr(true),
			HydraulicRHChecked:        boolPtr(true),
			OilTransmissionMGBChecked: boolPtr(true),
			OilTransmissionIGBChecked: boolPtr(false),
			OilTransmissionTGBChecked: boolPtr(true),
			FuelAmount:                floatPtr(1),
			FuelUnit:                  stringPtr(beforeflightinspection.FuelUnitLT),
			FuelAddedAmount:           floatPtr(0),
		},
		Prepare: requestdto.ReserveAcPrepareInspectionAttributes{
			NotamBriefing:         boolPtr(true),
			WeatherBriefing:       boolPtr(true),
			OperationalFlightPlan: boolPtr(false),
		},
	}

	gotID, err := newChecklistTestService(db).upsertTakeoverInspectionTx(db, nil, inspectionID, req, helicopterID, uuidv7.MustBytes())
	if err != nil {
		t.Fatalf("expected update inspection without file_checklists to pass, got %v", err)
	}
	if string(gotID) != string(inspectionID) {
		t.Fatalf("unexpected inspection id: %x", gotID)
	}
}

func TestEnsureHelicopterExistsTxDoesNotRequireDeletedAtColumn(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	helicopterID := uuidv7.MustBytes()

	if err := db.Exec(`CREATE TABLE helicopters (
		id BLOB PRIMARY KEY
	)`).Error; err != nil {
		t.Fatalf("create helicopters table: %v", err)
	}
	if err := db.Exec(fmt.Sprintf(`INSERT INTO helicopters (id) VALUES (X'%x')`, helicopterID)).Error; err != nil {
		t.Fatalf("seed helicopter: %v", err)
	}

	if err := ensureHelicopterExistsTx(db, helicopterID); err != nil {
		t.Fatalf("expected helicopter existence check to pass without deleted_at column, got %v", err)
	}
	if err := ensureHelicopterExistsTx(db, uuidv7.MustBytes()); err == nil {
		t.Fatalf("expected missing helicopter to fail")
	}
}

func TestUpsertTakeoverInspectionTxAllowsEmptyFileChecklistsOnUpdate(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	now := time.Now().UTC()
	helicopterID := uuidv7.MustBytes()
	inspectionID := uuidv7.MustBytes()
	inspectionDate := time.Date(2026, 6, 24, 0, 0, 0, 0, time.UTC)

	if err := db.Create(&flightinspection.FlightInspection{
		ID:             inspectionID,
		InspectionDate: inspectionDate,
		Status:         flightinspection.StatusDraft,
		CreatedAt:      now,
		UpdatedAt:      now,
	}).Error; err != nil {
		t.Fatalf("create inspection: %v", err)
	}
	templateFile := helicopterfile.HelicopterFile{
		ID:               uuidv7.MustBytes(),
		Section:          helicopterfile.SectionBeforeFirstFlightInspection,
		Position:         1,
		IsMandatory:      true,
		HelicopterID:     helicopterID,
		FileAttachmentID: uuidv7.MustBytes(),
		CreatedAt:        now,
		UpdatedAt:        now,
	}
	if err := db.Create(&templateFile).Error; err != nil {
		t.Fatalf("create helicopter file template: %v", err)
	}

	req := &requestdto.ReserveAcInspectionCreateAttributes{
		InspectionDate: "2026-06-24",
		Before: requestdto.ReserveAcBeforeInspectionAttributes{
			OilEngineNR1Checked:       boolPtr(true),
			OilEngineNR2Checked:       boolPtr(true),
			HydraulicLHChecked:        boolPtr(true),
			HydraulicRHChecked:        boolPtr(true),
			OilTransmissionMGBChecked: boolPtr(true),
			OilTransmissionIGBChecked: boolPtr(false),
			OilTransmissionTGBChecked: boolPtr(true),
			FuelAmount:                floatPtr(1),
			FuelUnit:                  stringPtr(beforeflightinspection.FuelUnitLT),
			FuelAddedAmount:           floatPtr(0),
			FileChecklist:             []requestdto.ReserveAcFileChecklistItem{},
		},
		Prepare: requestdto.ReserveAcPrepareInspectionAttributes{
			NotamBriefing:         boolPtr(true),
			WeatherBriefing:       boolPtr(true),
			OperationalFlightPlan: boolPtr(false),
			FileChecklist:         []requestdto.ReserveAcFileChecklistItem{},
		},
	}

	gotID, err := newChecklistTestService(db).upsertTakeoverInspectionTx(db, nil, inspectionID, req, helicopterID, uuidv7.MustBytes())
	if err != nil {
		t.Fatalf("expected update inspection with empty file_checklists to pass, got %v", err)
	}
	if string(gotID) != string(inspectionID) {
		t.Fatalf("unexpected inspection id: %x", gotID)
	}

	var checklistCount int64
	if err := db.Table("flight_inspection_file_checklists").Count(&checklistCount).Error; err != nil {
		t.Fatalf("count file checklists: %v", err)
	}
	if checklistCount != 0 {
		t.Fatalf("expected no checklist rows to be created on empty update, got %d", checklistCount)
	}
}

func boolPtr(v bool) *bool { return &v }

func floatPtr(v float64) *float64 { return &v }

func stringPtr(v string) *string { return &v }

func TestUpsertTakeoverChecklistItemsTxAllowsEmptyTemplateAndEmptyPayload(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	inspectionID := uuidv7.MustBytes()
	helicopterID := uuidv7.MustBytes()

	err := newChecklistTestService(db).upsertTakeoverChecklistItemsTx(db, inspectionID, helicopterID, "fpwb", nil, uuidv7.MustBytes())
	if err != nil {
		t.Fatalf("expected empty template and empty payload to pass, got %v", err)
	}
}

func TestUpsertTakeoverChecklistItemsTxRejectsItemsWhenTemplateMissing(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	inspectionID := uuidv7.MustBytes()
	helicopterID := uuidv7.MustBytes()
	fileID := uuidv7.MustBytes()
	fileIDStr, _ := uuidv7.BytesToString(fileID)

	err := newChecklistTestService(db).upsertTakeoverChecklistItemsTx(db, inspectionID, helicopterID, "fpwb", []requestdto.ReserveAcFileChecklistItem{{
		HelicopterFileID: fileIDStr,
		IsDone:           true,
	}}, uuidv7.MustBytes())
	if err == nil {
		t.Fatal("expected error when template is missing and payload has items")
	}
	if !strings.Contains(err.Error(), "inspection checklist template is empty for section fpwb") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestUpsertTakeoverChecklistItemsTxAllowsOptionalFalseButRequiresMandatoryTrue(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	inspectionID := uuidv7.MustBytes()
	helicopterID := uuidv7.MustBytes()
	now := time.Now().UTC()

	mandatoryFile := helicopterfile.HelicopterFile{
		ID:               uuidv7.MustBytes(),
		Section:          "fpwb",
		Position:         1,
		IsMandatory:      true,
		HelicopterID:     helicopterID,
		FileAttachmentID: uuidv7.MustBytes(),
		CreatedAt:        now,
		UpdatedAt:        now,
	}
	optionalFile := helicopterfile.HelicopterFile{
		ID:               uuidv7.MustBytes(),
		Section:          "fpwb",
		Position:         2,
		IsMandatory:      false,
		HelicopterID:     helicopterID,
		FileAttachmentID: uuidv7.MustBytes(),
		CreatedAt:        now,
		UpdatedAt:        now,
	}
	if err := db.Create(&mandatoryFile).Error; err != nil {
		t.Fatalf("create mandatory file: %v", err)
	}
	if err := db.Create(&optionalFile).Error; err != nil {
		t.Fatalf("create optional file: %v", err)
	}

	mandatoryIDStr, _ := uuidv7.BytesToString(mandatoryFile.ID)
	optionalIDStr, _ := uuidv7.BytesToString(optionalFile.ID)
	err := newChecklistTestService(db).upsertTakeoverChecklistItemsTx(db, inspectionID, helicopterID, "fpwb", []requestdto.ReserveAcFileChecklistItem{
		{HelicopterFileID: mandatoryIDStr, IsDone: true},
		{HelicopterFileID: optionalIDStr, IsDone: false},
	}, uuidv7.MustBytes())
	if err != nil {
		t.Fatalf("expected optional false to pass, got %v", err)
	}

	err = newChecklistTestService(db).upsertTakeoverChecklistItemsTx(db, inspectionID, helicopterID, "fpwb", []requestdto.ReserveAcFileChecklistItem{
		{HelicopterFileID: mandatoryIDStr, IsDone: false},
		{HelicopterFileID: optionalIDStr, IsDone: false},
	}, uuidv7.MustBytes())
	if err == nil {
		t.Fatal("expected mandatory false to fail")
	}
	if !strings.Contains(err.Error(), "must be fully checked") {
		t.Fatalf("unexpected error: %v", err)
	}
}

func TestUpsertTakeoverChecklistItemsTxAllowsPartialChecklist(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	inspectionID := uuidv7.MustBytes()
	helicopterID := uuidv7.MustBytes()
	now := time.Now().UTC()

	mandatoryFile := helicopterfile.HelicopterFile{
		ID: uuidv7.MustBytes(), Section: "fpwb", Position: 1, IsMandatory: true,
		HelicopterID: helicopterID, FileAttachmentID: uuidv7.MustBytes(), CreatedAt: now, UpdatedAt: now,
	}
	optionalFile := helicopterfile.HelicopterFile{
		ID: uuidv7.MustBytes(), Section: "fpwb", Position: 2, IsMandatory: false,
		HelicopterID: helicopterID, FileAttachmentID: uuidv7.MustBytes(), CreatedAt: now, UpdatedAt: now,
	}
	if err := db.Create(&mandatoryFile).Error; err != nil {
		t.Fatalf("create mandatory file: %v", err)
	}
	if err := db.Create(&optionalFile).Error; err != nil {
		t.Fatalf("create optional file: %v", err)
	}

	optionalIDStr, _ := uuidv7.BytesToString(optionalFile.ID)

	// Submit ONLY the optional file; the unedited mandatory catalog file may be
	// omitted from the checklist (no more "must include all helicopter files").
	if err := newChecklistTestService(db).upsertTakeoverChecklistItemsTx(db, inspectionID, helicopterID, "fpwb", []requestdto.ReserveAcFileChecklistItem{
		{HelicopterFileID: optionalIDStr, IsDone: true},
	}, uuidv7.MustBytes()); err != nil {
		t.Fatalf("expected partial checklist omitting non-edited files to pass, got %v", err)
	}

	// Only the submitted item is recorded.
	var count int64
	if err := db.Table("flight_inspection_file_checklists").Where("flight_inspection_id = ?", inspectionID).Count(&count).Error; err != nil {
		t.Fatalf("count checklist rows: %v", err)
	}
	if count != 1 {
		t.Fatalf("expected exactly 1 checklist row, got %d", count)
	}

	// A mandatory file that IS submitted but not done is still rejected.
	mandatoryIDStr, _ := uuidv7.BytesToString(mandatoryFile.ID)
	if err := newChecklistTestService(db).upsertTakeoverChecklistItemsTx(db, inspectionID, helicopterID, "fpwb", []requestdto.ReserveAcFileChecklistItem{
		{HelicopterFileID: mandatoryIDStr, IsDone: false},
	}, uuidv7.MustBytes()); err == nil {
		t.Fatal("expected submitted-but-undone mandatory file to be rejected")
	}
}

func TestUpsertTakeoverChecklistItemsTxExcludesTemplateDerivedFiles(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	if err := db.AutoMigrate(&filemanager.File{}); err != nil {
		t.Fatalf("migrate files: %v", err)
	}
	now := time.Now().UTC()
	helicopterID := uuidv7.MustBytes()
	inspectionID := uuidv7.MustBytes()

	templateFileID := uuidv7.MustBytes()
	derivedFileID := uuidv7.MustBytes()
	folderID := uuidv7.MustBytes()
	if err := db.Create(&filemanager.File{ID: templateFileID, FolderID: folderID, Name: "template.docx", ObjectKey: "k1", IsTemplate: true}).Error; err != nil {
		t.Fatalf("create template file: %v", err)
	}
	// A document created from a template (has a non-empty template_uuid).
	if err := db.Create(&filemanager.File{ID: derivedFileID, FolderID: folderID, Name: "from-template.docx", ObjectKey: "k2", TemplateUUID: uuidv7.MustBytes()}).Error; err != nil {
		t.Fatalf("create derived file: %v", err)
	}

	templateRow := helicopterfile.HelicopterFile{
		ID: uuidv7.MustBytes(), Section: "fpwb", Position: 1,
		HelicopterID: helicopterID, SourceFileID: templateFileID, CreatedAt: now, UpdatedAt: now,
	}
	derivedRow := helicopterfile.HelicopterFile{
		ID: uuidv7.MustBytes(), Section: "fpwb", Position: 2,
		HelicopterID: helicopterID, SourceFileID: derivedFileID, CreatedAt: now, UpdatedAt: now,
	}
	if err := db.Create(&templateRow).Error; err != nil {
		t.Fatalf("create template row: %v", err)
	}
	if err := db.Create(&derivedRow).Error; err != nil {
		t.Fatalf("create derived row: %v", err)
	}

	templateIDStr, _ := uuidv7.BytesToString(templateRow.ID)
	// Payload includes only the real template; the template-derived row must not be
	// required by the checklist completeness check.
	if err := newChecklistTestService(db).upsertTakeoverChecklistItemsTx(db, inspectionID, helicopterID, "fpwb", []requestdto.ReserveAcFileChecklistItem{
		{HelicopterFileID: templateIDStr, IsDone: true},
	}, uuidv7.MustBytes()); err != nil {
		t.Fatalf("expected success when only the template is checked, got %v", err)
	}
}

func TestValidateTakeoverEditedTemplateFilesTx(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	if err := db.AutoMigrate(&filemanager.File{}); err != nil {
		t.Fatalf("migrate files: %v", err)
	}
	svc := newChecklistTestService(db)
	takeoverID := uuidv7.MustBytes()
	helicopterID := uuidv7.MustBytes()
	folderID := uuidv7.MustBytes()
	now := time.Now().UTC()

	// template_uuid on a from-template document == the source HelicopterFile.ID
	// that a checklist item's helicopter_file_id references.
	editedTemplateA := uuidv7.MustBytes()
	editedTemplateB := uuidv7.MustBytes()
	if err := db.Create(&helicopterfile.HelicopterFile{
		ID:           editedTemplateA,
		Section:      helicopterfile.SectionFlightPreparationAndWB,
		HelicopterID: helicopterID,
		SourceFileID: uuidv7.MustBytes(),
		CreatedAt:    now,
		UpdatedAt:    now,
	}).Error; err != nil {
		t.Fatalf("create checklist template A: %v", err)
	}
	if err := db.Create(&helicopterfile.HelicopterFile{
		ID:           editedTemplateB,
		Section:      helicopterfile.SectionFlightPreparationAndWB,
		HelicopterID: helicopterID,
		SourceFileID: uuidv7.MustBytes(),
		CreatedAt:    now,
		UpdatedAt:    now,
	}).Error; err != nil {
		t.Fatalf("create checklist template B: %v", err)
	}

	// Two documents edited/created from a template for this takeover.
	if err := db.Create(&filemanager.File{ID: uuidv7.MustBytes(), FolderID: folderID, Name: "a.docx", ObjectKey: "a", TemplateUUID: editedTemplateA, TakeoverID: takeoverID}).Error; err != nil {
		t.Fatalf("create edited file A: %v", err)
	}
	if err := db.Create(&filemanager.File{ID: uuidv7.MustBytes(), FolderID: folderID, Name: "b.docx", ObjectKey: "b", TemplateUUID: editedTemplateB, TakeoverID: takeoverID}).Error; err != nil {
		t.Fatalf("create edited file B: %v", err)
	}
	// A from-template document for a different takeover must be ignored.
	if err := db.Create(&filemanager.File{ID: uuidv7.MustBytes(), FolderID: folderID, Name: "other.docx", ObjectKey: "o", TemplateUUID: uuidv7.MustBytes(), TakeoverID: uuidv7.MustBytes()}).Error; err != nil {
		t.Fatalf("create other-takeover file: %v", err)
	}

	aStr, _ := uuidv7.BytesToString(editedTemplateA)
	bStr, _ := uuidv7.BytesToString(editedTemplateB)
	unrelatedStr, _ := uuidv7.BytesToString(uuidv7.MustBytes())

	// 1) Checklist missing an edited file (B) -> reject.
	if err := svc.validateTakeoverEditedTemplateFilesTx(db, takeoverID, helicopterID, []requestdto.ReserveAcFileChecklistItem{
		{HelicopterFileID: aStr, IsDone: true},
	}); err == nil {
		t.Fatalf("expected rejection when an edited file is missing from the checklist")
	}

	// 2) Edited files exist but checklist is empty -> reject.
	if err := svc.validateTakeoverEditedTemplateFilesTx(db, takeoverID, helicopterID, nil); err == nil {
		t.Fatalf("expected rejection when edited files exist but checklist is empty")
	}

	// 3) Checklist includes both edited files plus an unrelated (non-edited)
	//    item -> success; a checklist item that was not edited is fine.
	if err := svc.validateTakeoverEditedTemplateFilesTx(db, takeoverID, helicopterID, []requestdto.ReserveAcFileChecklistItem{
		{HelicopterFileID: aStr, IsDone: true},
		{HelicopterFileID: bStr, IsDone: true},
		{HelicopterFileID: unrelatedStr, IsDone: false},
	}); err != nil {
		t.Fatalf("expected success when all edited files are in the checklist, got %v", err)
	}

	// 4) No edited files for the takeover -> success regardless of checklist.
	if err := svc.validateTakeoverEditedTemplateFilesTx(db, uuidv7.MustBytes(), uuidv7.MustBytes(), nil); err != nil {
		t.Fatalf("expected success when there are no edited files, got %v", err)
	}

	// 5) A template-derived file that has already been trashed should not block
	//    an inspection update when it is no longer part of the active takeover set.
	trashedTemplate := uuidv7.MustBytes()
	trashedTakeoverID := uuidv7.MustBytes()
	if err := db.Create(&filemanager.File{
		ID:           uuidv7.MustBytes(),
		FolderID:     folderID,
		Name:         "trashed.docx",
		ObjectKey:    "trashed",
		TemplateUUID: trashedTemplate,
		TakeoverID:   trashedTakeoverID,
		Status:       filemanager.FileStatusTrashed,
	}).Error; err != nil {
		t.Fatalf("create trashed edited file: %v", err)
	}
	if err := svc.validateTakeoverEditedTemplateFilesTx(db, trashedTakeoverID, helicopterID, nil); err != nil {
		t.Fatalf("expected trashed edited file to be ignored, got %v", err)
	}
}

func TestValidateTakeoverEditedTemplateFilesTxAcceptsLegacySourceFileReferences(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	if err := db.AutoMigrate(&filemanager.File{}); err != nil {
		t.Fatalf("migrate files: %v", err)
	}
	svc := newChecklistTestService(db)
	takeoverID := uuidv7.MustBytes()
	helicopterID := uuidv7.MustBytes()
	folderID := uuidv7.MustBytes()
	now := time.Now().UTC()

	sourceFileID := uuidv7.MustBytes()
	templateHeliFileID := uuidv7.MustBytes()
	if err := db.Create(&filemanager.File{
		ID:        sourceFileID,
		FolderID:  folderID,
		Name:      "template.xlsx",
		ObjectKey: "template.xlsx",
	}).Error; err != nil {
		t.Fatalf("create source template file: %v", err)
	}
	if err := db.Create(&helicopterfile.HelicopterFile{
		ID:           templateHeliFileID,
		Section:      helicopterfile.SectionFlightPreparationAndWB,
		HelicopterID: helicopterID,
		SourceFileID: sourceFileID,
		CreatedAt:    now,
		UpdatedAt:    now,
	}).Error; err != nil {
		t.Fatalf("create helicopter template row: %v", err)
	}

	// Legacy data may have template_uuid pointing to the source file ID instead
	// of the helicopter_file ID shown in the checklist payload.
	if err := db.Create(&filemanager.File{
		ID:           uuidv7.MustBytes(),
		FolderID:     folderID,
		Name:         "draft.xlsx",
		ObjectKey:    "draft.xlsx",
		TemplateUUID: sourceFileID,
		TakeoverID:   takeoverID,
	}).Error; err != nil {
		t.Fatalf("create legacy edited file: %v", err)
	}

	templateChecklistID, _ := uuidv7.BytesToString(templateHeliFileID)
	if err := svc.validateTakeoverEditedTemplateFilesTx(db, takeoverID, helicopterID, []requestdto.ReserveAcFileChecklistItem{
		{HelicopterFileID: templateChecklistID, IsDone: true},
	}); err != nil {
		t.Fatalf("expected legacy source file template_uuid to map back to checklist template, got %v", err)
	}
}

func TestValidateTakeoverEditedTemplateFilesTxIgnoresOrphanedTemplateReferences(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	if err := db.AutoMigrate(&filemanager.File{}); err != nil {
		t.Fatalf("migrate files: %v", err)
	}
	svc := newChecklistTestService(db)
	takeoverID := uuidv7.MustBytes()
	helicopterID := uuidv7.MustBytes()
	folderID := uuidv7.MustBytes()

	if err := db.Create(&filemanager.File{
		ID:           uuidv7.MustBytes(),
		FolderID:     folderID,
		Name:         "orphaned-draft.xlsx",
		ObjectKey:    "orphaned-draft.xlsx",
		TemplateUUID: uuidv7.MustBytes(),
		TakeoverID:   takeoverID,
	}).Error; err != nil {
		t.Fatalf("create orphaned edited file: %v", err)
	}

	if err := svc.validateTakeoverEditedTemplateFilesTx(db, takeoverID, helicopterID, nil); err != nil {
		t.Fatalf("expected orphaned historical template reference to be ignored, got %v", err)
	}
}

func TestResolveBaseShiftRangeTxPrefersOperationalShiftTime(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	now := time.Now().UTC()
	category := &base.BaseCategory{ID: uuidv7.MustBytes(), Key: base.CategoryKeyRegular, Name: "Regular", CreatedAt: now, UpdatedAt: now}
	if err := db.Create(category).Error; err != nil {
		t.Fatalf("create category: %v", err)
	}
	defaultStart := time.Date(1900, 1, 1, 9, 0, 0, 0, time.UTC)
	defaultEnd := time.Date(1900, 1, 1, 18, 0, 0, 0, time.UTC)
	baseID := uuidv7.MustBytes()
	row := &base.Base{
		ID:                baseID,
		BaseCategoryID:    category.ID,
		BaseName:          "Base A",
		DefaultShiftStart: defaultStart.Format("15:04:05"),
		DefaultShiftEnd:   defaultEnd.Format("15:04:05"),
		CreatedAt:         now,
		UpdatedAt:         now,
	}
	if err := db.Create(row).Error; err != nil {
		t.Fatalf("create base: %v", err)
	}
	opStart := time.Date(1900, 1, 1, 9, 0, 0, 0, time.UTC)
	opEnd := time.Date(1900, 1, 1, 17, 0, 0, 0, time.UTC)
	opDate := time.Date(2026, 4, 12, 0, 0, 0, 0, time.UTC)
	opRow := &base.BaseOperationalShiftTime{
		ID:         uuidv7.MustBytes(),
		BaseID:     baseID,
		DateStart:  &opDate,
		DateEnd:    &opDate,
		ShiftStart: opStart.Format("15:04:05"),
		ShiftEnd:   opEnd.Format("15:04:05"),
		CreatedAt:  now,
		UpdatedAt:  now,
	}
	if err := db.Create(opRow).Error; err != nil {
		t.Fatalf("create operational shift: %v", err)
	}

	start, end, err := ResolveBaseShiftRangeTx(db, baseID, time.Date(2026, 4, 12, 0, 0, 0, 0, time.UTC))
	if err != nil {
		t.Fatalf("resolve shift: %v", err)
	}
	if start != "09:00" || end != "17:00" {
		t.Fatalf("unexpected operational shift: start=%s end=%s", start, end)
	}
}

func TestResolveBaseShiftRangeTxFallsBackToDefaultShiftTime(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	now := time.Now().UTC()
	category := &base.BaseCategory{ID: uuidv7.MustBytes(), Key: base.CategoryKeyRegular, Name: "Regular", CreatedAt: now, UpdatedAt: now}
	if err := db.Create(category).Error; err != nil {
		t.Fatalf("create category: %v", err)
	}
	defaultStart := time.Date(1900, 1, 1, 9, 0, 0, 0, time.UTC)
	defaultEnd := time.Date(1900, 1, 1, 18, 0, 0, 0, time.UTC)
	baseID := uuidv7.MustBytes()
	row := &base.Base{
		ID:                baseID,
		BaseCategoryID:    category.ID,
		BaseName:          "Base B",
		DefaultShiftStart: defaultStart.Format("15:04:05"),
		DefaultShiftEnd:   defaultEnd.Format("15:04:05"),
		CreatedAt:         now,
		UpdatedAt:         now,
	}
	if err := db.Create(row).Error; err != nil {
		t.Fatalf("create base: %v", err)
	}

	start, end, err := ResolveBaseShiftRangeTx(db, baseID, time.Date(2026, 4, 13, 0, 0, 0, 0, time.UTC))
	if err != nil {
		t.Fatalf("resolve shift: %v", err)
	}
	if start != "09:00" || end != "18:00" {
		t.Fatalf("unexpected default shift: start=%s end=%s", start, end)
	}
}

func TestResolveBaseShiftRangeTxResolvesBMCTECETFromCoordinates(t *testing.T) {
	db := openTakeoverServiceTestDB(t)
	now := time.Now().UTC()
	category := &base.BaseCategory{ID: uuidv7.MustBytes(), Key: base.CategoryKeyRegular, Name: "Regular", CreatedAt: now, UpdatedAt: now}
	if err := db.Create(category).Error; err != nil {
		t.Fatalf("create category: %v", err)
	}
	baseID := uuidv7.MustBytes()
	row := &base.Base{
		ID:                   baseID,
		BaseCategoryID:       category.ID,
		BaseName:             "Base C",
		Latitude:             47.3769,
		Longitude:            8.5417,
		DefaultStartTimeType: base.ShiftTimeTypeBMCT,
		DefaultEndTimeType:   base.ShiftTimeTypeECET,
		CreatedAt:            now,
		UpdatedAt:            now,
	}
	if err := db.Create(row).Error; err != nil {
		t.Fatalf("create base: %v", err)
	}

	start, end, err := ResolveBaseShiftRangeTx(db, baseID, time.Date(2026, 4, 13, 0, 0, 0, 0, time.UTC))
	if err != nil {
		t.Fatalf("resolve shift: %v", err)
	}
	if strings.TrimSpace(start) == "" || strings.TrimSpace(end) == "" {
		t.Fatalf("expected resolved BMCT/ECET shift, got start=%q end=%q", start, end)
	}
	if start == "09:00:00" || end == "18:00:00" {
		t.Fatalf("expected computed twilight times, got fixed-looking values start=%s end=%s", start, end)
	}
}
