package mysql

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

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

	airrescuechecklist "wucher/internal/domain/air_rescue_checklist"
	basedomain "wucher/internal/domain/base"
	"wucher/internal/shared/pkg/uuidv7"
)

func openAirRescuerChecklistRepoTestDB(t *testing.T) *gorm.DB {
	t.Helper()
	dsn := fmt.Sprintf("file:air_rescuer_checklist_repo_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)
	}

	createBaseCategory := "CREATE TABLE base_categories (\n\tid BLOB PRIMARY KEY,\n\t`key` TEXT UNIQUE,\n\tname TEXT NOT NULL\n);"
	if err := db.Exec(createBaseCategory).Error; err != nil {
		t.Fatalf("create base_categories: %v", err)
	}

	createBase := `
CREATE TABLE bases (
	id BLOB PRIMARY KEY,
	base_category_id BLOB NOT NULL,
	base TEXT NOT NULL,
	base_abbreviation TEXT NULL,
	sortkey INTEGER NULL,
	checklist INTEGER NOT NULL DEFAULT 0,
	is_active INTEGER NOT NULL DEFAULT 1
);`
	if err := db.Exec(createBase).Error; err != nil {
		t.Fatalf("create bases: %v", err)
	}

	createChecklist := `
CREATE TABLE air_rescuer_checklists (
	id BLOB PRIMARY KEY,
	hems_base_id BLOB NOT NULL,
	scope_code TEXT NOT NULL,
	title TEXT NOT NULL,
	position INTEGER NOT NULL DEFAULT 1,
	created_at DATETIME NULL,
	created_by BLOB NULL,
	updated_at DATETIME NULL,
	updated_by BLOB NULL,
	deleted_at DATETIME NULL,
	deleted_by BLOB NULL
);`
	if err := db.Exec(createChecklist).Error; err != nil {
		t.Fatalf("create air_rescuer_checklists: %v", err)
	}

	createItems := `
CREATE TABLE air_rescuer_checklist_items (
	id BLOB PRIMARY KEY,
	checklist_id BLOB NOT NULL,
	name TEXT NOT NULL,
	position INTEGER NOT NULL DEFAULT 1,
	created_at DATETIME NULL,
	created_by BLOB NULL,
	updated_at DATETIME NULL,
	updated_by BLOB NULL,
	deleted_at DATETIME NULL,
	deleted_by BLOB NULL
);`
	if err := db.Exec(createItems).Error; err != nil {
		t.Fatalf("create air_rescuer_checklist_items: %v", err)
	}
	regularCategoryID, hemsCategoryID := airRescuerChecklistCategoryIDs()
	toBlob := func(b []byte) string { return fmt.Sprintf("X'%x'", b) }
	if err := db.Exec(
		fmt.Sprintf(
			"INSERT INTO base_categories(id, `key`, name) VALUES(%s, 'regular', 'Regular'),(%s, 'hems', 'HEMS')",
			toBlob(regularCategoryID),
			toBlob(hemsCategoryID),
		),
	).Error; err != nil {
		t.Fatalf("seed base_categories: %v", err)
	}

	return db
}

func airRescuerChecklistCategoryIDs() (regularID, hemsID []byte) {
	return []byte("catregular000001"), []byte("cathems000000001")
}

func TestAirRescuerChecklistRepositoryListChecklistHeaders_SortsByPositionThenTitle(t *testing.T) {
	db := openAirRescuerChecklistRepoTestDB(t)
	repo := NewAirRescuerChecklistRepository(db)
	ctx := context.Background()

	baseID := uuidv7.MustBytes()
	now := time.Now().UTC()
	asBlob := func(b []byte) string { return fmt.Sprintf("X'%x'", b) }
	_, hemsCategoryID := airRescuerChecklistCategoryIDs()
	if err := db.Exec(
		fmt.Sprintf(`INSERT INTO bases(id, base_category_id, base, base_abbreviation, sortkey, checklist, is_active) VALUES(%s, %s, ?, ?, ?, 1, ?)`, asBlob(baseID), asBlob(hemsCategoryID)),
		"Gallus 1 - Zurs Lech", "G1", 1, 1,
	).Error; err != nil {
		t.Fatalf("insert bases: %v", err)
	}

	// Seed deliberately out of desired display order. Duplicate position=1 is allowed.
	rows := []struct {
		id       []byte
		scope    string
		title    string
		position int
	}{
		{id: uuidv7.MustBytes(), scope: airrescuechecklist.AirRescuerChecklistScopeTA, title: "Zulu", position: 2},
		{id: uuidv7.MustBytes(), scope: airrescuechecklist.AirRescuerChecklistScopeTA, title: "Bravo", position: 1},
		{id: uuidv7.MustBytes(), scope: airrescuechecklist.AirRescuerChecklistScopeTA, title: "Alpha", position: 1},
	}
	for i := range rows {
		if err := db.Exec(
			fmt.Sprintf(`INSERT INTO air_rescuer_checklists(id, hems_base_id, scope_code, title, position, created_at, updated_at, deleted_at) VALUES (%s, %s, ?, ?, ?, ?, ?, NULL)`,
				asBlob(rows[i].id), asBlob(baseID)),
			rows[i].scope, rows[i].title, rows[i].position, now, now,
		).Error; err != nil {
			t.Fatalf("insert checklist[%d]: %v", i, err)
		}
	}

	got, err := repo.ListChecklistHeaders(ctx, airrescuechecklist.ChecklistHeaderFilter{
		HEMSBaseID: baseID,
		ScopeCode:  airrescuechecklist.AirRescuerChecklistScopeTA,
	})
	if err != nil {
		t.Fatalf("ListChecklistHeaders: %v", err)
	}
	if len(got) != 3 {
		t.Fatalf("expected 3 rows, got %d", len(got))
	}

	wantTitles := []string{"Alpha", "Bravo", "Zulu"}
	wantPos := []int{1, 1, 2}
	for i := range got {
		if got[i].Title != wantTitles[i] || got[i].Position != wantPos[i] {
			t.Fatalf("unexpected row[%d]: got title=%q pos=%d, want title=%q pos=%d", i, got[i].Title, got[i].Position, wantTitles[i], wantPos[i])
		}
	}
}

func TestAirRescuerChecklistRepositoryListChecklistItemsByChecklistIDs_SortsByPositionThenName(t *testing.T) {
	db := openAirRescuerChecklistRepoTestDB(t)
	repo := NewAirRescuerChecklistRepository(db)
	ctx := context.Background()

	baseID := uuidv7.MustBytes()
	checklistID := uuidv7.MustBytes()
	now := time.Now().UTC()
	asBlob := func(b []byte) string { return fmt.Sprintf("X'%x'", b) }
	_, hemsCategoryID := airRescuerChecklistCategoryIDs()

	if err := db.Exec(
		fmt.Sprintf(`INSERT INTO bases(id, base_category_id, base, base_abbreviation, sortkey, checklist, is_active) VALUES(%s, %s, ?, ?, ?, 1, ?)`, asBlob(baseID), asBlob(hemsCategoryID)),
		"Gallus 1 - Zurs Lech", "G1", 1, 1,
	).Error; err != nil {
		t.Fatalf("insert bases: %v", err)
	}
	if err := db.Exec(
		fmt.Sprintf(`INSERT INTO air_rescuer_checklists(id, hems_base_id, scope_code, title, position, created_at, updated_at, deleted_at) VALUES (%s, %s, ?, ?, ?, ?, ?, NULL)`,
			asBlob(checklistID), asBlob(baseID)),
		airrescuechecklist.AirRescuerChecklistScopeTA, "Daily Checks", 1, now, now,
	).Error; err != nil {
		t.Fatalf("insert checklist: %v", err)
	}

	// Seed deliberately out of display order. Duplicate position=2 is allowed.
	items := []struct {
		id       []byte
		name     string
		position int
	}{
		{id: uuidv7.MustBytes(), name: "Radio", position: 2},
		{id: uuidv7.MustBytes(), name: "Battery", position: 2},
		{id: uuidv7.MustBytes(), name: "Oxygen", position: 1},
	}
	for i := range items {
		if err := db.Exec(
			fmt.Sprintf(`INSERT INTO air_rescuer_checklist_items(id, checklist_id, name, position, created_at, updated_at, deleted_at) VALUES (%s, %s, ?, ?, ?, ?, NULL)`,
				asBlob(items[i].id), asBlob(checklistID)),
			items[i].name, items[i].position, now, now,
		).Error; err != nil {
			t.Fatalf("insert item[%d]: %v", i, err)
		}
	}

	got, err := repo.ListChecklistItemsByChecklistIDs(ctx, [][]byte{checklistID})
	if err != nil {
		t.Fatalf("ListChecklistItemsByChecklistIDs: %v", err)
	}
	if len(got) != 3 {
		t.Fatalf("expected 3 rows, got %d", len(got))
	}

	wantNames := []string{"Oxygen", "Battery", "Radio"}
	wantPos := []int{1, 2, 2}
	for i := range got {
		if got[i].Name != wantNames[i] || got[i].Position != wantPos[i] {
			t.Fatalf("unexpected row[%d]: got name=%q pos=%d, want name=%q pos=%d", i, got[i].Name, got[i].Position, wantNames[i], wantPos[i])
		}
	}
}

func TestAirRescuerChecklistRepositoryInTxCommitAndRollback(t *testing.T) {
	db := openAirRescuerChecklistRepoTestDB(t)
	repo := NewAirRescuerChecklistRepository(db)
	ctx := context.Background()

	baseID := uuidv7.MustBytes()
	mustInsertHEMSBase(t, db, baseID, "Gallus 1 - Zurs Lech", "G1", 1, true)

	checklistID := uuidv7.MustBytes()
	if err := repo.InTx(ctx, func(txRepo airrescuechecklist.TxRepository) error {
		return txRepo.CreateChecklist(ctx, &airrescuechecklist.AirRescuerChecklist{
			ID:         append([]byte(nil), checklistID...),
			HEMSBaseID: append([]byte(nil), baseID...),
			ScopeCode:  airrescuechecklist.AirRescuerChecklistScopeTA,
			Title:      "Daily Tasks",
			Position:   1,
		})
	}); err != nil {
		t.Fatalf("InTx commit: %v", err)
	}

	if _, err := repo.LockChecklistByID(ctx, checklistID); err != nil {
		t.Fatalf("expected committed checklist to exist: %v", err)
	}

	rollbackID := uuidv7.MustBytes()
	rollbackErr := errors.New("rollback me")
	err := repo.InTx(ctx, func(txRepo airrescuechecklist.TxRepository) error {
		if err := txRepo.CreateChecklist(ctx, &airrescuechecklist.AirRescuerChecklist{
			ID:         append([]byte(nil), rollbackID...),
			HEMSBaseID: append([]byte(nil), baseID...),
			ScopeCode:  airrescuechecklist.AirRescuerChecklistScopeMO,
			Title:      "Monday Tasks",
			Position:   1,
		}); err != nil {
			return err
		}
		return rollbackErr
	})
	if !errors.Is(err, rollbackErr) {
		t.Fatalf("expected rollback error, got %v", err)
	}

	if _, err := repo.LockChecklistByID(ctx, rollbackID); !errors.Is(err, gorm.ErrRecordNotFound) {
		t.Fatalf("expected rolled back checklist missing, got %v", err)
	}
}

func TestAirRescuerChecklistRepositoryChecklistCRUD(t *testing.T) {
	db := openAirRescuerChecklistRepoTestDB(t)
	repo := NewAirRescuerChecklistRepository(db)
	ctx := context.Background()
	now := time.Now().UTC()

	baseID := uuidv7.MustBytes()
	mustInsertHEMSBase(t, db, baseID, "Gallus 1 - Zurs Lech", "G1", 1, true)

	checklistID := uuidv7.MustBytes()
	if err := repo.CreateChecklist(ctx, &airrescuechecklist.AirRescuerChecklist{
		ID:         append([]byte(nil), checklistID...),
		HEMSBaseID: append([]byte(nil), baseID...),
		ScopeCode:  airrescuechecklist.AirRescuerChecklistScopeTA,
		Title:      "Original",
		Position:   2,
		CreatedAt:  now,
		UpdatedAt:  now,
	}); err != nil {
		t.Fatalf("CreateChecklist: %v", err)
	}

	locked, err := repo.LockChecklistByID(ctx, checklistID)
	if err != nil {
		t.Fatalf("LockChecklistByID: %v", err)
	}
	if locked.Title != "Original" {
		t.Fatalf("unexpected title after lock: %s", locked.Title)
	}

	updatedBy := uuidv7.MustBytes()
	if err := repo.UpdateChecklist(ctx, &airrescuechecklist.AirRescuerChecklist{
		ID:        append([]byte(nil), checklistID...),
		ScopeCode: airrescuechecklist.AirRescuerChecklistScopeMO,
		Title:     "Updated",
		Position:  1,
		UpdatedBy: updatedBy,
	}); err != nil {
		t.Fatalf("UpdateChecklist: %v", err)
	}

	afterUpdate, err := repo.GetChecklistByID(ctx, checklistID)
	if err != nil {
		t.Fatalf("GetChecklistByID after update: %v", err)
	}
	if afterUpdate == nil || afterUpdate.ScopeCode != airrescuechecklist.AirRescuerChecklistScopeMO || afterUpdate.Title != "Updated" || afterUpdate.Position != 1 {
		t.Fatalf("unexpected checklist after update: %+v", afterUpdate)
	}

	notFoundID := uuidv7.MustBytes()
	if err := repo.UpdateChecklist(ctx, &airrescuechecklist.AirRescuerChecklist{ID: notFoundID}); !errors.Is(err, gorm.ErrRecordNotFound) {
		t.Fatalf("expected not found on update, got %v", err)
	}

	deletedBy := uuidv7.MustBytes()
	if err := repo.SoftDeleteChecklist(ctx, checklistID, deletedBy); err != nil {
		t.Fatalf("SoftDeleteChecklist: %v", err)
	}
	if err := repo.SoftDeleteChecklist(ctx, checklistID, deletedBy); !errors.Is(err, gorm.ErrRecordNotFound) {
		t.Fatalf("expected not found on double delete, got %v", err)
	}
	if _, err := repo.LockChecklistByID(ctx, checklistID); !errors.Is(err, gorm.ErrRecordNotFound) {
		t.Fatalf("expected deleted checklist cannot be locked, got %v", err)
	}
}

func TestAirRescuerChecklistRepositoryChecklistItemCRUD(t *testing.T) {
	db := openAirRescuerChecklistRepoTestDB(t)
	repo := NewAirRescuerChecklistRepository(db)
	ctx := context.Background()
	now := time.Now().UTC()

	baseID := uuidv7.MustBytes()
	checklistID := uuidv7.MustBytes()
	mustInsertHEMSBase(t, db, baseID, "Gallus 1 - Zurs Lech", "G1", 1, true)
	mustInsertChecklist(t, db, checklistID, baseID, airrescuechecklist.AirRescuerChecklistScopeTA, "Daily", 1, now)

	itemID := uuidv7.MustBytes()
	if err := repo.CreateChecklistItem(ctx, &airrescuechecklist.AirRescuerChecklistItem{
		ID:          append([]byte(nil), itemID...),
		ChecklistID: append([]byte(nil), checklistID...),
		Name:        "Radio",
		Position:    2,
		CreatedAt:   now,
		UpdatedAt:   now,
	}); err != nil {
		t.Fatalf("CreateChecklistItem: %v", err)
	}

	if err := repo.CreateChecklistItems(ctx, nil); err != nil {
		t.Fatalf("CreateChecklistItems empty: %v", err)
	}

	secondID := uuidv7.MustBytes()
	thirdID := uuidv7.MustBytes()
	if err := repo.CreateChecklistItems(ctx, []airrescuechecklist.AirRescuerChecklistItem{
		{ID: append([]byte(nil), secondID...), ChecklistID: append([]byte(nil), checklistID...), Name: "Battery", Position: 1, CreatedAt: now, UpdatedAt: now},
		{ID: append([]byte(nil), thirdID...), ChecklistID: append([]byte(nil), checklistID...), Name: "Oxygen", Position: 3, CreatedAt: now, UpdatedAt: now},
	}); err != nil {
		t.Fatalf("CreateChecklistItems: %v", err)
	}

	locked, err := repo.LockChecklistItemByID(ctx, itemID)
	if err != nil {
		t.Fatalf("LockChecklistItemByID: %v", err)
	}
	if locked.Name != "Radio" {
		t.Fatalf("unexpected locked item: %+v", locked)
	}

	if err := repo.UpdateChecklistItem(ctx, &airrescuechecklist.AirRescuerChecklistItem{
		ID:       append([]byte(nil), itemID...),
		Name:     "Radio Updated",
		Position: 5,
	}); err != nil {
		t.Fatalf("UpdateChecklistItem: %v", err)
	}
	gotItem, err := repo.GetChecklistItemByID(ctx, itemID)
	if err != nil {
		t.Fatalf("GetChecklistItemByID after update: %v", err)
	}
	if gotItem == nil || gotItem.Name != "Radio Updated" || gotItem.Position != 5 {
		t.Fatalf("unexpected item after update: %+v", gotItem)
	}

	notFoundID := uuidv7.MustBytes()
	if err := repo.UpdateChecklistItem(ctx, &airrescuechecklist.AirRescuerChecklistItem{ID: notFoundID}); !errors.Is(err, gorm.ErrRecordNotFound) {
		t.Fatalf("expected not found on update item, got %v", err)
	}

	deletedBy := uuidv7.MustBytes()
	if err := repo.SoftDeleteChecklistItem(ctx, itemID, deletedBy); err != nil {
		t.Fatalf("SoftDeleteChecklistItem: %v", err)
	}
	if err := repo.SoftDeleteChecklistItem(ctx, itemID, deletedBy); !errors.Is(err, gorm.ErrRecordNotFound) {
		t.Fatalf("expected not found on double item delete, got %v", err)
	}
	if _, err := repo.LockChecklistItemByID(ctx, itemID); !errors.Is(err, gorm.ErrRecordNotFound) {
		t.Fatalf("expected deleted item cannot be locked, got %v", err)
	}

	if err := repo.SoftDeleteItemsByChecklistID(ctx, checklistID, deletedBy); err != nil {
		t.Fatalf("SoftDeleteItemsByChecklistID: %v", err)
	}
	remaining, err := repo.ListChecklistItemsByChecklistIDs(ctx, [][]byte{checklistID})
	if err != nil {
		t.Fatalf("ListChecklistItemsByChecklistIDs after soft delete all: %v", err)
	}
	if len(remaining) != 0 {
		t.Fatalf("expected no active items after bulk soft delete, got %d", len(remaining))
	}
}

func TestAirRescuerChecklistRepositoryGetAndListFilters(t *testing.T) {
	db := openAirRescuerChecklistRepoTestDB(t)
	repo := NewAirRescuerChecklistRepository(db)
	ctx := context.Background()
	now := time.Now().UTC()

	baseA := uuidv7.MustBytes()
	baseB := uuidv7.MustBytes()
	baseRegular := uuidv7.MustBytes()
	baseInactive := uuidv7.MustBytes()
	mustInsertHEMSBase(t, db, baseA, "Gallus 1 - Zurs Lech", "G1", 2, true)
	mustInsertHEMSBase(t, db, baseB, "Gallus 2 - Innsbruck", "G2", 1, true)
	mustInsertRegularBase(t, db, baseRegular, "Regular Base", "RB", 4, true, true)
	mustInsertHEMSBase(t, db, baseInactive, "Inactive Base", "IN", 3, false)

	checklistA1 := uuidv7.MustBytes()
	checklistA2 := uuidv7.MustBytes()
	checklistB := uuidv7.MustBytes()
	checklistRegular := uuidv7.MustBytes()
	mustInsertChecklist(t, db, checklistA1, baseA, airrescuechecklist.AirRescuerChecklistScopeTA, "Alpha", 1, now)
	mustInsertChecklist(t, db, checklistA2, baseA, airrescuechecklist.AirRescuerChecklistScopeTA, "Bravo", 2, now)
	mustInsertChecklist(t, db, checklistB, baseB, airrescuechecklist.AirRescuerChecklistScopeMO, "Monday", 1, now)
	mustInsertChecklist(t, db, checklistRegular, baseRegular, airrescuechecklist.AirRescuerChecklistScopeTA, "Regular Daily", 1, now)

	itemA := uuidv7.MustBytes()
	itemB := uuidv7.MustBytes()
	mustInsertChecklistItem(t, db, itemA, checklistA1, "Battery", 1, now)
	mustInsertChecklistItem(t, db, itemB, checklistA1, "Radio", 2, now)

	got, err := repo.GetChecklistByID(ctx, checklistA1)
	if err != nil {
		t.Fatalf("GetChecklistByID: %v", err)
	}
	if got == nil || got.BaseName != "Gallus 1 - Zurs Lech" {
		t.Fatalf("unexpected checklist header view: %+v", got)
	}
	missingChecklist, err := repo.GetChecklistByID(ctx, uuidv7.MustBytes())
	if err != nil {
		t.Fatalf("GetChecklistByID missing: %v", err)
	}
	if missingChecklist != nil {
		t.Fatalf("expected nil for missing checklist, got %+v", missingChecklist)
	}

	gotItem, err := repo.GetChecklistItemByID(ctx, itemA)
	if err != nil {
		t.Fatalf("GetChecklistItemByID: %v", err)
	}
	if gotItem == nil || gotItem.Name != "Battery" {
		t.Fatalf("unexpected checklist item view: %+v", gotItem)
	}
	missingItem, err := repo.GetChecklistItemByID(ctx, uuidv7.MustBytes())
	if err != nil {
		t.Fatalf("GetChecklistItemByID missing: %v", err)
	}
	if missingItem != nil {
		t.Fatalf("expected nil for missing item, got %+v", missingItem)
	}

	headersByScope, err := repo.ListChecklistHeaders(ctx, airrescuechecklist.ChecklistHeaderFilter{
		HEMSBaseID:       append([]byte(nil), baseA...),
		ScopeCode:        strings.ToLower(airrescuechecklist.AirRescuerChecklistScopeTA),
		BaseName:         "zurs",
		BaseAbbreviation: "g1",
	})
	if err != nil {
		t.Fatalf("ListChecklistHeaders by scope/base filters: %v", err)
	}
	if len(headersByScope) != 2 {
		t.Fatalf("expected 2 TA headers for baseA, got %d", len(headersByScope))
	}

	headersRegular, err := repo.ListChecklistHeaders(ctx, airrescuechecklist.ChecklistHeaderFilter{
		CategoryType: basedomain.CategoryKeyRegular,
		ScopeCode:    airrescuechecklist.AirRescuerChecklistScopeTA,
	})
	if err != nil {
		t.Fatalf("ListChecklistHeaders regular category: %v", err)
	}
	if len(headersRegular) != 1 || headersRegular[0].Title != "Regular Daily" {
		t.Fatalf("expected regular checklist header only, got %+v", headersRegular)
	}

	orderedBases, err := repo.ListHEMSBases(ctx, airrescuechecklist.HEMSBaseFilter{})
	if err != nil {
		t.Fatalf("ListHEMSBases all: %v", err)
	}
	if len(orderedBases) != 3 {
		t.Fatalf("expected only active bases, got %d", len(orderedBases))
	}
	if orderedBases[0].BaseName != "Gallus 2 - Innsbruck" || orderedBases[1].BaseName != "Gallus 1 - Zurs Lech" || orderedBases[2].BaseName != "Regular Base" {
		t.Fatalf("unexpected base ordering: %+v", orderedBases)
	}

	hemsOnly, err := repo.ListHEMSBases(ctx, airrescuechecklist.HEMSBaseFilter{CategoryType: basedomain.CategoryKeyHEMS})
	if err != nil {
		t.Fatalf("ListHEMSBases hems only: %v", err)
	}
	if len(hemsOnly) != 2 {
		t.Fatalf("expected 2 hems bases, got %d", len(hemsOnly))
	}

	regularOnly, err := repo.ListHEMSBases(ctx, airrescuechecklist.HEMSBaseFilter{CategoryType: basedomain.CategoryKeyRegular})
	if err != nil {
		t.Fatalf("ListHEMSBases regular only: %v", err)
	}
	if len(regularOnly) != 1 || regularOnly[0].BaseName != "Regular Base" {
		t.Fatalf("expected 1 regular base, got %+v", regularOnly)
	}

	filteredBase, err := repo.ListHEMSBases(ctx, airrescuechecklist.HEMSBaseFilter{
		HEMSBaseID:       append([]byte(nil), baseA...),
		BaseName:         "zurs",
		BaseAbbreviation: "g1",
	})
	if err != nil {
		t.Fatalf("ListHEMSBases filtered: %v", err)
	}
	if len(filteredBase) != 1 || filteredBase[0].ID == nil {
		t.Fatalf("unexpected filtered base rows: %+v", filteredBase)
	}

	emptyItems, err := repo.ListChecklistItemsByChecklistIDs(ctx, nil)
	if err != nil {
		t.Fatalf("ListChecklistItemsByChecklistIDs empty ids: %v", err)
	}
	if len(emptyItems) != 0 {
		t.Fatalf("expected empty items for empty checklist ids, got %d", len(emptyItems))
	}
}

func TestAirRescuerChecklistRepositoryDatabaseErrorBranches(t *testing.T) {
	ctx := context.Background()
	id := uuidv7.MustBytes()

	t.Run("update checklist db error", func(t *testing.T) {
		db := openAirRescuerChecklistRepoTestDB(t)
		repo := NewAirRescuerChecklistRepository(db)
		if err := db.Exec(`DROP TABLE air_rescuer_checklists`).Error; err != nil {
			t.Fatalf("drop table: %v", err)
		}
		if err := repo.UpdateChecklist(ctx, &airrescuechecklist.AirRescuerChecklist{ID: id, Title: "x"}); err == nil {
			t.Fatalf("expected db error")
		}
		if err := repo.SoftDeleteChecklist(ctx, id, uuidv7.MustBytes()); err == nil {
			t.Fatalf("expected db error")
		}
		if _, err := repo.GetChecklistByID(ctx, id); err == nil {
			t.Fatalf("expected db error")
		}
		if _, err := repo.ListChecklistHeaders(ctx, airrescuechecklist.ChecklistHeaderFilter{}); err == nil {
			t.Fatalf("expected db error")
		}
	})

	t.Run("item db error", func(t *testing.T) {
		db := openAirRescuerChecklistRepoTestDB(t)
		repo := NewAirRescuerChecklistRepository(db)
		if err := db.Exec(`DROP TABLE air_rescuer_checklist_items`).Error; err != nil {
			t.Fatalf("drop table: %v", err)
		}
		if err := repo.UpdateChecklistItem(ctx, &airrescuechecklist.AirRescuerChecklistItem{ID: id, Name: "x"}); err == nil {
			t.Fatalf("expected db error")
		}
		if err := repo.SoftDeleteChecklistItem(ctx, id, uuidv7.MustBytes()); err == nil {
			t.Fatalf("expected db error")
		}
		if _, err := repo.GetChecklistItemByID(ctx, id); err == nil {
			t.Fatalf("expected db error")
		}
		if _, err := repo.ListChecklistItemsByChecklistIDs(ctx, [][]byte{id}); err == nil {
			t.Fatalf("expected db error")
		}
	})

	t.Run("list hems bases db error", func(t *testing.T) {
		db := openAirRescuerChecklistRepoTestDB(t)
		repo := NewAirRescuerChecklistRepository(db)
		if err := db.Exec(`DROP TABLE bases`).Error; err != nil {
			t.Fatalf("drop table: %v", err)
		}
		if _, err := repo.ListHEMSBases(ctx, airrescuechecklist.HEMSBaseFilter{}); err == nil {
			t.Fatalf("expected db error")
		}
	})
}

func mustInsertHEMSBase(t *testing.T, db *gorm.DB, id []byte, name, abbr string, position int, active bool) {
	t.Helper()
	toBlob := func(b []byte) string { return fmt.Sprintf("X'%x'", b) }
	_, hemsCategoryID := airRescuerChecklistCategoryIDs()
	if err := db.Exec(
		fmt.Sprintf(`INSERT INTO bases(id, base_category_id, base, base_abbreviation, sortkey, checklist, is_active) VALUES (%s, %s, ?, ?, ?, 1, ?)`, toBlob(id), toBlob(hemsCategoryID)),
		name, abbr, position, active,
	).Error; err != nil {
		t.Fatalf("insert hems base: %v", err)
	}
}

func mustInsertRegularBase(t *testing.T, db *gorm.DB, id []byte, name, abbr string, position int, active bool, checklist bool) {
	t.Helper()
	toBlob := func(b []byte) string { return fmt.Sprintf("X'%x'", b) }
	regularCategoryID, _ := airRescuerChecklistCategoryIDs()
	checklistVal := 0
	if checklist {
		checklistVal = 1
	}
	activeVal := 0
	if active {
		activeVal = 1
	}
	if err := db.Exec(
		fmt.Sprintf(`INSERT INTO bases(id, base_category_id, base, base_abbreviation, sortkey, checklist, is_active) VALUES (%s, %s, ?, ?, ?, ?, ?)`, toBlob(id), toBlob(regularCategoryID)),
		name, abbr, position, checklistVal, activeVal,
	).Error; err != nil {
		t.Fatalf("insert regular base: %v", err)
	}
}

func mustInsertChecklist(t *testing.T, db *gorm.DB, id, baseID []byte, scopeCode, title string, position int, now time.Time) {
	t.Helper()
	toBlob := func(b []byte) string { return fmt.Sprintf("X'%x'", b) }
	if err := db.Exec(
		fmt.Sprintf(`INSERT INTO air_rescuer_checklists(id, hems_base_id, scope_code, title, position, created_at, updated_at, deleted_at) VALUES (%s, %s, ?, ?, ?, ?, ?, NULL)`, toBlob(id), toBlob(baseID)),
		scopeCode, title, position, now, now,
	).Error; err != nil {
		t.Fatalf("insert checklist: %v", err)
	}
}

func mustInsertChecklistItem(t *testing.T, db *gorm.DB, id, checklistID []byte, name string, position int, now time.Time) {
	t.Helper()
	toBlob := func(b []byte) string { return fmt.Sprintf("X'%x'", b) }
	if err := db.Exec(
		fmt.Sprintf(`INSERT INTO air_rescuer_checklist_items(id, checklist_id, name, position, created_at, updated_at, deleted_at) VALUES (%s, %s, ?, ?, ?, ?, NULL)`, toBlob(id), toBlob(checklistID)),
		name, position, now, now,
	).Error; err != nil {
		t.Fatalf("insert checklist item: %v", err)
	}
}
