package service

import (
	"bytes"
	"context"
	"errors"
	"testing"

	"gorm.io/gorm"

	airrescuechecklist "wucher/internal/domain/air_rescue_checklist"
)

type airRescuerChecklistTxRepoMock struct {
	createChecklistCalled bool
	createChecklistRow    *airrescuechecklist.AirRescuerChecklist
	createChecklistErr    error
	nextChecklistPosition int
	nextChecklistPosErr   error
	nextChecklistPosCalls int

	lockChecklistByIDCalled bool
	lockChecklistID         []byte
	lockChecklistByIDErr    error

	updateChecklistCalled bool
	updateChecklistRow    *airrescuechecklist.AirRescuerChecklist
	updateChecklistErr    error

	softDeleteChecklistCalled bool
	softDeleteChecklistID     []byte
	softDeleteChecklistBy     []byte
	softDeleteChecklistErr    error

	softDeleteItemsByChecklistCalled bool
	softDeleteItemsByChecklistID     []byte
	softDeleteItemsByChecklistBy     []byte
	softDeleteItemsByChecklistErr    error

	createChecklistItemCalled bool
	createChecklistItemRow    *airrescuechecklist.AirRescuerChecklistItem
	createChecklistItemErr    error

	createChecklistItemsCalled bool
	createChecklistItemsRows   []airrescuechecklist.AirRescuerChecklistItem
	createChecklistItemsErr    error
	nextChecklistItemPosition  int
	nextChecklistItemPosErr    error
	nextChecklistItemPosCalls  int

	lockChecklistItemByIDCalled bool
	lockChecklistItemID         []byte
	lockChecklistItemIDs        [][]byte
	lockChecklistItemRows       map[string]*airrescuechecklist.AirRescuerChecklistItem
	lockChecklistItemByIDErr    error

	updateChecklistItemCalled bool
	updateChecklistItemRow    *airrescuechecklist.AirRescuerChecklistItem
	updateChecklistItemErr    error

	softDeleteChecklistItemCalled bool
	softDeleteChecklistItemID     []byte
	softDeleteChecklistItemBy     []byte
	softDeleteChecklistItemErr    error
}

func (m *airRescuerChecklistTxRepoMock) CreateChecklist(_ context.Context, row *airrescuechecklist.AirRescuerChecklist) error {
	m.createChecklistCalled = true
	m.createChecklistRow = row
	return m.createChecklistErr
}

func (m *airRescuerChecklistTxRepoMock) NextChecklistPosition(_ context.Context, _ []byte, _ string) (int, error) {
	m.nextChecklistPosCalls++
	if m.nextChecklistPosErr != nil {
		return 0, m.nextChecklistPosErr
	}
	if m.nextChecklistPosition > 0 {
		return m.nextChecklistPosition, nil
	}
	return 1, nil
}

func (m *airRescuerChecklistTxRepoMock) LockChecklistByID(_ context.Context, id []byte) (*airrescuechecklist.AirRescuerChecklist, error) {
	m.lockChecklistByIDCalled = true
	m.lockChecklistID = append([]byte(nil), id...)
	if m.lockChecklistByIDErr != nil {
		return nil, m.lockChecklistByIDErr
	}
	return &airrescuechecklist.AirRescuerChecklist{ID: append([]byte(nil), id...)}, nil
}

func (m *airRescuerChecklistTxRepoMock) UpdateChecklist(_ context.Context, row *airrescuechecklist.AirRescuerChecklist) error {
	m.updateChecklistCalled = true
	m.updateChecklistRow = row
	return m.updateChecklistErr
}

func (m *airRescuerChecklistTxRepoMock) SoftDeleteChecklist(_ context.Context, id []byte, deletedBy []byte) error {
	m.softDeleteChecklistCalled = true
	m.softDeleteChecklistID = append([]byte(nil), id...)
	m.softDeleteChecklistBy = append([]byte(nil), deletedBy...)
	return m.softDeleteChecklistErr
}

func (m *airRescuerChecklistTxRepoMock) SoftDeleteItemsByChecklistID(_ context.Context, checklistID []byte, deletedBy []byte) error {
	m.softDeleteItemsByChecklistCalled = true
	m.softDeleteItemsByChecklistID = append([]byte(nil), checklistID...)
	m.softDeleteItemsByChecklistBy = append([]byte(nil), deletedBy...)
	return m.softDeleteItemsByChecklistErr
}

func (m *airRescuerChecklistTxRepoMock) CreateChecklistItem(_ context.Context, row *airrescuechecklist.AirRescuerChecklistItem) error {
	m.createChecklistItemCalled = true
	m.createChecklistItemRow = row
	return m.createChecklistItemErr
}

func (m *airRescuerChecklistTxRepoMock) CreateChecklistItems(_ context.Context, rows []airrescuechecklist.AirRescuerChecklistItem) error {
	m.createChecklistItemsCalled = true
	m.createChecklistItemsRows = append([]airrescuechecklist.AirRescuerChecklistItem(nil), rows...)
	return m.createChecklistItemsErr
}

func (m *airRescuerChecklistTxRepoMock) NextChecklistItemPosition(_ context.Context, _ []byte) (int, error) {
	m.nextChecklistItemPosCalls++
	if m.nextChecklistItemPosErr != nil {
		return 0, m.nextChecklistItemPosErr
	}
	if m.nextChecklistItemPosition > 0 {
		return m.nextChecklistItemPosition, nil
	}
	return 1, nil
}

func (m *airRescuerChecklistTxRepoMock) LockChecklistItemByID(_ context.Context, id []byte) (*airrescuechecklist.AirRescuerChecklistItem, error) {
	m.lockChecklistItemByIDCalled = true
	m.lockChecklistItemID = append([]byte(nil), id...)
	m.lockChecklistItemIDs = append(m.lockChecklistItemIDs, append([]byte(nil), id...))
	if m.lockChecklistItemByIDErr != nil {
		return nil, m.lockChecklistItemByIDErr
	}
	if m.lockChecklistItemRows != nil {
		if row, ok := m.lockChecklistItemRows[string(id)]; ok {
			dup := *row
			dup.ID = append([]byte(nil), row.ID...)
			dup.ChecklistID = append([]byte(nil), row.ChecklistID...)
			return &dup, nil
		}
	}
	return &airrescuechecklist.AirRescuerChecklistItem{ID: append([]byte(nil), id...)}, nil
}

func (m *airRescuerChecklistTxRepoMock) UpdateChecklistItem(_ context.Context, row *airrescuechecklist.AirRescuerChecklistItem) error {
	m.updateChecklistItemCalled = true
	m.updateChecklistItemRow = row
	return m.updateChecklistItemErr
}

func (m *airRescuerChecklistTxRepoMock) SoftDeleteChecklistItem(_ context.Context, id []byte, deletedBy []byte) error {
	m.softDeleteChecklistItemCalled = true
	m.softDeleteChecklistItemID = append([]byte(nil), id...)
	m.softDeleteChecklistItemBy = append([]byte(nil), deletedBy...)
	return m.softDeleteChecklistItemErr
}

type airRescuerChecklistRepoMock struct {
	txRepo     *airRescuerChecklistTxRepoMock
	inTxCalled bool

	getChecklistByIDCalled bool
	getChecklistByIDID     []byte
	getChecklistByIDRow    *airrescuechecklist.ChecklistHeaderView
	getChecklistByIDErr    error

	getChecklistItemByIDCalled bool
	getChecklistItemByIDID     []byte
	getChecklistItemByIDRow    *airrescuechecklist.ChecklistItemView
	getChecklistItemByIDErr    error

	listChecklistHeadersCalled bool
	listChecklistHeadersFilter airrescuechecklist.ChecklistHeaderFilter
	listChecklistHeadersRows   []airrescuechecklist.ChecklistHeaderView
	listChecklistHeadersErr    error

	listChecklistItemsByIDsCalled bool
	listChecklistItemsByIDsArg    [][]byte
	listChecklistItemsByIDsRows   []airrescuechecklist.ChecklistItemView
	listChecklistItemsByIDsErr    error

	listHEMSBasesCalled bool
	listHEMSBasesFilter airrescuechecklist.HEMSBaseFilter
	listHEMSBasesRows   []airrescuechecklist.HEMSBaseView
	listHEMSBasesErr    error
}

func (m *airRescuerChecklistRepoMock) InTx(ctx context.Context, fn func(txRepo airrescuechecklist.TxRepository) error) error {
	m.inTxCalled = true
	return fn(m.txRepo)
}

func (m *airRescuerChecklistRepoMock) GetChecklistByID(_ context.Context, id []byte) (*airrescuechecklist.ChecklistHeaderView, error) {
	m.getChecklistByIDCalled = true
	m.getChecklistByIDID = append([]byte(nil), id...)
	return m.getChecklistByIDRow, m.getChecklistByIDErr
}

func (m *airRescuerChecklistRepoMock) GetChecklistItemByID(_ context.Context, id []byte) (*airrescuechecklist.ChecklistItemView, error) {
	m.getChecklistItemByIDCalled = true
	m.getChecklistItemByIDID = append([]byte(nil), id...)
	return m.getChecklistItemByIDRow, m.getChecklistItemByIDErr
}

func (m *airRescuerChecklistRepoMock) ListChecklistHeaders(_ context.Context, filter airrescuechecklist.ChecklistHeaderFilter) ([]airrescuechecklist.ChecklistHeaderView, error) {
	m.listChecklistHeadersCalled = true
	m.listChecklistHeadersFilter = filter
	return m.listChecklistHeadersRows, m.listChecklistHeadersErr
}

func (m *airRescuerChecklistRepoMock) ListChecklistItemsByChecklistIDs(_ context.Context, checklistIDs [][]byte) ([]airrescuechecklist.ChecklistItemView, error) {
	m.listChecklistItemsByIDsCalled = true
	m.listChecklistItemsByIDsArg = append([][]byte(nil), checklistIDs...)
	return m.listChecklistItemsByIDsRows, m.listChecklistItemsByIDsErr
}

func (m *airRescuerChecklistRepoMock) ListHEMSBases(_ context.Context, filter airrescuechecklist.HEMSBaseFilter) ([]airrescuechecklist.HEMSBaseView, error) {
	m.listHEMSBasesCalled = true
	m.listHEMSBasesFilter = filter
	return m.listHEMSBasesRows, m.listHEMSBasesErr
}

func TestAirRescuerChecklistServiceCreateChecklistItems(t *testing.T) {
	checklistID := []byte("1234567890123456")
	txRepo := &airRescuerChecklistTxRepoMock{nextChecklistItemPosition: 7}
	repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
	svc := NewAirRescuerChecklistService(repo)

	rows := []airrescuechecklist.AirRescuerChecklistItem{
		{Name: "Check oxygen", Position: 0},
		{Name: "Check radio", Position: 2},
	}

	if err := svc.CreateChecklistItems(context.Background(), checklistID, rows); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !repo.inTxCalled {
		t.Fatalf("expected CreateChecklistItems to run in transaction")
	}
	if !txRepo.lockChecklistByIDCalled {
		t.Fatalf("expected checklist lock before create")
	}
	if !bytes.Equal(txRepo.lockChecklistID, checklistID) {
		t.Fatalf("expected lock checklist id forwarded from path param")
	}
	if !txRepo.createChecklistItemsCalled {
		t.Fatalf("expected bulk create items called")
	}
	if len(txRepo.createChecklistItemsRows) != 2 {
		t.Fatalf("expected 2 rows, got %d", len(txRepo.createChecklistItemsRows))
	}
	if !bytes.Equal(txRepo.createChecklistItemsRows[0].ChecklistID, checklistID) || !bytes.Equal(txRepo.createChecklistItemsRows[1].ChecklistID, checklistID) {
		t.Fatalf("expected checklist_id derived from path for all rows")
	}
	if txRepo.createChecklistItemsRows[0].Position != 7 {
		t.Fatalf("expected first position auto-assigned to 7, got %d", txRepo.createChecklistItemsRows[0].Position)
	}
	if txRepo.createChecklistItemsRows[1].Position != 2 {
		t.Fatalf("expected second position stay 2, got %d", txRepo.createChecklistItemsRows[1].Position)
	}
	if txRepo.nextChecklistItemPosCalls != 1 {
		t.Fatalf("expected next checklist item position to be read once, got %d", txRepo.nextChecklistItemPosCalls)
	}
}

func TestAirRescuerChecklistServiceCreateChecklistItems_AllowsDuplicatePosition(t *testing.T) {
	checklistID := []byte("1234567890123456")
	txRepo := &airRescuerChecklistTxRepoMock{}
	repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
	svc := NewAirRescuerChecklistService(repo)

	rows := []airrescuechecklist.AirRescuerChecklistItem{
		{Name: "Battery", Position: 2},
		{Name: "Radio", Position: 2},
	}

	if err := svc.CreateChecklistItems(context.Background(), checklistID, rows); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !txRepo.createChecklistItemsCalled {
		t.Fatalf("expected bulk create called")
	}
	if len(txRepo.createChecklistItemsRows) != 2 {
		t.Fatalf("expected 2 rows, got %d", len(txRepo.createChecklistItemsRows))
	}
	if txRepo.createChecklistItemsRows[0].Position != 2 || txRepo.createChecklistItemsRows[1].Position != 2 {
		t.Fatalf("expected duplicate position to be preserved, got %d and %d", txRepo.createChecklistItemsRows[0].Position, txRepo.createChecklistItemsRows[1].Position)
	}
	if txRepo.nextChecklistItemPosCalls != 0 {
		t.Fatalf("expected no auto position query when all positions are provided")
	}
}

func TestAirRescuerChecklistServiceCreateChecklistItemsErrorBranches(t *testing.T) {
	checklistID := []byte("1234567890123456")

	t.Run("empty rows", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		if err := svc.CreateChecklistItems(context.Background(), checklistID, nil); err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if repo.inTxCalled {
			t.Fatalf("expected no transaction when rows are empty")
		}
	})

	t.Run("lock checklist failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{lockChecklistByIDErr: errors.New("lock failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		err := svc.CreateChecklistItems(context.Background(), checklistID, []airrescuechecklist.AirRescuerChecklistItem{{Name: "A"}})
		if err == nil || err.Error() != "lock failed" {
			t.Fatalf("expected lock error, got %v", err)
		}
		if txRepo.createChecklistItemsCalled {
			t.Fatalf("expected create items not called when lock fails")
		}
	})

	t.Run("create bulk failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{createChecklistItemsErr: errors.New("create failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		err := svc.CreateChecklistItems(context.Background(), checklistID, []airrescuechecklist.AirRescuerChecklistItem{{Name: "A"}})
		if err == nil || err.Error() != "create failed" {
			t.Fatalf("expected create error, got %v", err)
		}
	})
}

func TestAirRescuerChecklistServiceReorderChecklistItems(t *testing.T) {
	checklistID := []byte("1234567890123456")
	itemA := []byte("1111111111111111")
	itemB := []byte("2222222222222222")
	updatedBy := []byte("aaaaaaaaaaaaaaaa")

	txRepo := &airRescuerChecklistTxRepoMock{
		lockChecklistItemRows: map[string]*airrescuechecklist.AirRescuerChecklistItem{
			string(itemA): {ID: append([]byte(nil), itemA...), ChecklistID: append([]byte(nil), checklistID...), Name: "Item A", Position: 1},
			string(itemB): {ID: append([]byte(nil), itemB...), ChecklistID: append([]byte(nil), checklistID...), Name: "Item B", Position: 2},
		},
	}
	repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
	svc := NewAirRescuerChecklistService(repo)

	err := svc.ReorderChecklistItems(context.Background(), checklistID, []airrescuechecklist.ChecklistItemReorderInput{
		{ID: append([]byte(nil), itemA...), Position: 2},
		{ID: append([]byte(nil), itemB...), Position: 1},
	}, updatedBy)
	if err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !repo.inTxCalled {
		t.Fatalf("expected transaction for reorder")
	}
	if !txRepo.lockChecklistByIDCalled {
		t.Fatalf("expected checklist lock for reorder")
	}
	if len(txRepo.lockChecklistItemIDs) != 2 {
		t.Fatalf("expected two item locks, got %d", len(txRepo.lockChecklistItemIDs))
	}
	if !txRepo.updateChecklistItemCalled {
		t.Fatalf("expected item updates to be executed")
	}
	if txRepo.updateChecklistItemRow == nil {
		t.Fatalf("expected update row to be set")
	}
	if txRepo.updateChecklistItemRow.Position != 1 {
		t.Fatalf("expected last update position to be 1, got %d", txRepo.updateChecklistItemRow.Position)
	}
	if !bytes.Equal(txRepo.updateChecklistItemRow.UpdatedBy, updatedBy) {
		t.Fatalf("expected updated_by to be propagated")
	}
}

func TestAirRescuerChecklistServiceReorderChecklistItemsBranches(t *testing.T) {
	checklistID := []byte("1234567890123456")
	itemID := []byte("1111111111111111")
	otherChecklistID := []byte("2222222222222222")

	t.Run("empty rows", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		err := svc.ReorderChecklistItems(context.Background(), checklistID, nil, nil)
		if !errors.Is(err, gorm.ErrInvalidData) {
			t.Fatalf("expected invalid data, got %v", err)
		}
		if repo.inTxCalled {
			t.Fatalf("expected no transaction when rows are empty")
		}
	})

	t.Run("lock checklist failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{lockChecklistByIDErr: errors.New("lock checklist failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		err := svc.ReorderChecklistItems(context.Background(), checklistID, []airrescuechecklist.ChecklistItemReorderInput{{ID: itemID, Position: 1}}, nil)
		if err == nil || err.Error() != "lock checklist failed" {
			t.Fatalf("expected checklist lock error, got %v", err)
		}
	})

	t.Run("duplicate item ids in payload", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{
			lockChecklistItemRows: map[string]*airrescuechecklist.AirRescuerChecklistItem{
				string(itemID): {ID: append([]byte(nil), itemID...), ChecklistID: append([]byte(nil), checklistID...), Name: "Item", Position: 1},
			},
		}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		err := svc.ReorderChecklistItems(context.Background(), checklistID, []airrescuechecklist.ChecklistItemReorderInput{
			{ID: append([]byte(nil), itemID...), Position: 1},
			{ID: append([]byte(nil), itemID...), Position: 2},
		}, nil)
		if err == nil || err.Error() != "duplicate item_uuid in reorder payload" {
			t.Fatalf("expected duplicate item error, got %v", err)
		}
	})

	t.Run("item belongs to different checklist", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{
			lockChecklistItemRows: map[string]*airrescuechecklist.AirRescuerChecklistItem{
				string(itemID): {ID: append([]byte(nil), itemID...), ChecklistID: append([]byte(nil), otherChecklistID...), Name: "Item", Position: 1},
			},
		}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		err := svc.ReorderChecklistItems(context.Background(), checklistID, []airrescuechecklist.ChecklistItemReorderInput{{ID: itemID, Position: 3}}, nil)
		if !errors.Is(err, gorm.ErrRecordNotFound) {
			t.Fatalf("expected not found for different checklist item, got %v", err)
		}
	})

	t.Run("update item failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{
			lockChecklistItemRows: map[string]*airrescuechecklist.AirRescuerChecklistItem{
				string(itemID): {ID: append([]byte(nil), itemID...), ChecklistID: append([]byte(nil), checklistID...), Name: "Item", Position: 1},
			},
			updateChecklistItemErr: errors.New("update failed"),
		}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		err := svc.ReorderChecklistItems(context.Background(), checklistID, []airrescuechecklist.ChecklistItemReorderInput{{ID: itemID, Position: 1}}, nil)
		if err == nil || err.Error() != "update failed" {
			t.Fatalf("expected update failure, got %v", err)
		}
	})
}

func TestAirRescuerChecklistServiceUpdateChecklistWithNewItems(t *testing.T) {
	checklistID := []byte("1234567890123456")
	txRepo := &airRescuerChecklistTxRepoMock{nextChecklistItemPosition: 8}
	repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
	svc := NewAirRescuerChecklistService(repo)

	row := &airrescuechecklist.AirRescuerChecklist{
		ID:       append([]byte(nil), checklistID...),
		Position: 0,
	}
	newItems := []airrescuechecklist.AirRescuerChecklistItem{
		{Name: "Check battery", Position: 0},
		{Name: "Check stretcher lock", Position: 4},
	}

	if err := svc.UpdateChecklistWithNewItems(context.Background(), row, newItems); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !repo.inTxCalled {
		t.Fatalf("expected update flow to run in transaction")
	}
	if !txRepo.lockChecklistByIDCalled {
		t.Fatalf("expected checklist lock")
	}
	if !bytes.Equal(txRepo.lockChecklistID, checklistID) {
		t.Fatalf("expected lock ID from checklist")
	}
	if !txRepo.updateChecklistCalled {
		t.Fatalf("expected header update called")
	}
	if txRepo.updateChecklistRow == nil || txRepo.updateChecklistRow.Position != 1 {
		t.Fatalf("expected header position normalized to 1")
	}
	if !txRepo.createChecklistItemsCalled {
		t.Fatalf("expected new items append called")
	}
	if len(txRepo.createChecklistItemsRows) != 2 {
		t.Fatalf("expected 2 appended items")
	}
	if !bytes.Equal(txRepo.createChecklistItemsRows[0].ChecklistID, checklistID) || !bytes.Equal(txRepo.createChecklistItemsRows[1].ChecklistID, checklistID) {
		t.Fatalf("expected all new_items to use checklist ID")
	}
	if txRepo.createChecklistItemsRows[0].Position != 8 {
		t.Fatalf("expected first new item position auto-assigned to 8")
	}
	if txRepo.createChecklistItemsRows[1].Position != 4 {
		t.Fatalf("expected second new item position remains 4")
	}
	if txRepo.nextChecklistItemPosCalls != 1 {
		t.Fatalf("expected next checklist item position to be read once, got %d", txRepo.nextChecklistItemPosCalls)
	}
}

func TestAirRescuerChecklistServiceUpdateChecklistWithNewItemsBranches(t *testing.T) {
	checklistID := []byte("1234567890123456")

	t.Run("new-items-only", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		err := svc.UpdateChecklistWithNewItems(
			context.Background(),
			nil,
			[]airrescuechecklist.AirRescuerChecklistItem{{ChecklistID: append([]byte(nil), checklistID...), Name: "A"}},
		)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if txRepo.updateChecklistCalled {
			t.Fatalf("expected no header update when row is nil")
		}
		if !txRepo.createChecklistItemsCalled {
			t.Fatalf("expected append items called")
		}
	})

	t.Run("header-only", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		err := svc.UpdateChecklistWithNewItems(
			context.Background(),
			&airrescuechecklist.AirRescuerChecklist{ID: append([]byte(nil), checklistID...), Position: 2},
			nil,
		)
		if err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if !txRepo.updateChecklistCalled {
			t.Fatalf("expected header update called")
		}
		if txRepo.createChecklistItemsCalled {
			t.Fatalf("expected no append items when new_items empty")
		}
	})

	t.Run("invalid payload", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		if err := svc.UpdateChecklistWithNewItems(context.Background(), nil, nil); err == nil {
			t.Fatalf("expected invalid data error")
		}
	})

	t.Run("lock failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{lockChecklistByIDErr: errors.New("lock failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		err := svc.UpdateChecklistWithNewItems(context.Background(), &airrescuechecklist.AirRescuerChecklist{ID: append([]byte(nil), checklistID...), Position: 1}, nil)
		if err == nil || err.Error() != "lock failed" {
			t.Fatalf("expected lock error, got %v", err)
		}
	})

	t.Run("update failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{updateChecklistErr: errors.New("update failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		err := svc.UpdateChecklistWithNewItems(context.Background(), &airrescuechecklist.AirRescuerChecklist{ID: append([]byte(nil), checklistID...), Position: 1}, nil)
		if err == nil || err.Error() != "update failed" {
			t.Fatalf("expected update error, got %v", err)
		}
		if txRepo.createChecklistItemsCalled {
			t.Fatalf("expected append not called when update failed")
		}
	})

	t.Run("append failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{createChecklistItemsErr: errors.New("append failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)

		err := svc.UpdateChecklistWithNewItems(
			context.Background(),
			&airrescuechecklist.AirRescuerChecklist{ID: append([]byte(nil), checklistID...), Position: 1},
			[]airrescuechecklist.AirRescuerChecklistItem{{Name: "A"}},
		)
		if err == nil || err.Error() != "append failed" {
			t.Fatalf("expected append error, got %v", err)
		}
	})
}

func TestAirRescuerChecklistServiceUpdateChecklistWithNewItemsInvalidChecklistIDFromItems(t *testing.T) {
	txRepo := &airRescuerChecklistTxRepoMock{nextChecklistPosition: 5, nextChecklistItemPosition: 9}
	repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
	svc := NewAirRescuerChecklistService(repo)

	err := svc.UpdateChecklistWithNewItems(context.Background(), nil, []airrescuechecklist.AirRescuerChecklistItem{{Name: "A"}})
	if err == nil {
		t.Fatalf("expected invalid data error when new_items checklist id is empty")
	}
}

func TestAirRescuerChecklistServiceCreateBulk(t *testing.T) {
	checklistID := []byte("1234567890123456")
	txRepo := &airRescuerChecklistTxRepoMock{nextChecklistPosition: 5, nextChecklistItemPosition: 9}
	repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
	svc := NewAirRescuerChecklistService(repo)

	rows := []airrescuechecklist.ChecklistCreateInput{
		{
			Checklist: airrescuechecklist.AirRescuerChecklist{
				ID:       append([]byte(nil), checklistID...),
				Position: 0,
			},
			Items: []airrescuechecklist.AirRescuerChecklistItem{
				{Name: "Oxygen", Position: 0},
			},
		},
	}
	if err := svc.CreateBulk(context.Background(), rows); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !repo.inTxCalled || !txRepo.createChecklistCalled || !txRepo.createChecklistItemCalled {
		t.Fatalf("expected checklist and item created in transaction")
	}
	if txRepo.createChecklistRow == nil || txRepo.createChecklistRow.Position != 5 {
		t.Fatalf("expected checklist position auto-assigned to 5")
	}
	if txRepo.createChecklistItemRow == nil || txRepo.createChecklistItemRow.Position != 9 {
		t.Fatalf("expected item position auto-assigned to 9")
	}
	if !bytes.Equal(txRepo.createChecklistItemRow.ChecklistID, checklistID) {
		t.Fatalf("expected item checklist id copied from created checklist")
	}
	if txRepo.nextChecklistPosCalls != 1 {
		t.Fatalf("expected next checklist position to be read once, got %d", txRepo.nextChecklistPosCalls)
	}
	if txRepo.nextChecklistItemPosCalls != 1 {
		t.Fatalf("expected next checklist item position to be read once, got %d", txRepo.nextChecklistItemPosCalls)
	}
}

func TestAirRescuerChecklistServiceCreateBulkBranches(t *testing.T) {
	t.Run("empty rows", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		if err := svc.CreateBulk(context.Background(), nil); err != nil {
			t.Fatalf("unexpected error: %v", err)
		}
		if repo.inTxCalled {
			t.Fatalf("expected no transaction for empty rows")
		}
	})

	t.Run("create checklist failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{createChecklistErr: errors.New("create checklist failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		err := svc.CreateBulk(context.Background(), []airrescuechecklist.ChecklistCreateInput{{Checklist: airrescuechecklist.AirRescuerChecklist{}}})
		if err == nil || err.Error() != "create checklist failed" {
			t.Fatalf("expected create checklist error, got %v", err)
		}
	})

	t.Run("create item failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{createChecklistItemErr: errors.New("create item failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		err := svc.CreateBulk(context.Background(), []airrescuechecklist.ChecklistCreateInput{{
			Checklist: airrescuechecklist.AirRescuerChecklist{},
			Items:     []airrescuechecklist.AirRescuerChecklistItem{{Name: "A"}},
		}})
		if err == nil || err.Error() != "create item failed" {
			t.Fatalf("expected create item error, got %v", err)
		}
	})
}

func TestAirRescuerChecklistServiceUpdateChecklist(t *testing.T) {
	checklistID := []byte("1234567890123456")
	txRepo := &airRescuerChecklistTxRepoMock{nextChecklistItemPosition: 6}
	repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
	svc := NewAirRescuerChecklistService(repo)

	row := &airrescuechecklist.AirRescuerChecklist{ID: append([]byte(nil), checklistID...), Position: 0}
	if err := svc.UpdateChecklist(context.Background(), row); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if row.Position != 1 {
		t.Fatalf("expected position normalized to 1")
	}
	if !txRepo.lockChecklistByIDCalled || !txRepo.updateChecklistCalled {
		t.Fatalf("expected lock + update executed")
	}
}

func TestAirRescuerChecklistServiceUpdateChecklistBranches(t *testing.T) {
	t.Run("nil row", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		if err := svc.UpdateChecklist(context.Background(), nil); err == nil {
			t.Fatalf("expected invalid data error")
		}
	})

	t.Run("lock failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{lockChecklistByIDErr: errors.New("lock failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		err := svc.UpdateChecklist(context.Background(), &airrescuechecklist.AirRescuerChecklist{ID: []byte("1234567890123456"), Position: 1})
		if err == nil || err.Error() != "lock failed" {
			t.Fatalf("expected lock error, got %v", err)
		}
	})

	t.Run("update failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{updateChecklistErr: errors.New("update failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		err := svc.UpdateChecklist(context.Background(), &airrescuechecklist.AirRescuerChecklist{ID: []byte("1234567890123456"), Position: 1})
		if err == nil || err.Error() != "update failed" {
			t.Fatalf("expected update error, got %v", err)
		}
	})
}

func TestAirRescuerChecklistServiceDeleteChecklist(t *testing.T) {
	id := []byte("1234567890123456")
	deletedBy := []byte("abcdefghijklmnop")
	txRepo := &airRescuerChecklistTxRepoMock{}
	repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
	svc := NewAirRescuerChecklistService(repo)

	if err := svc.DeleteChecklist(context.Background(), id, deletedBy); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !txRepo.lockChecklistByIDCalled || !txRepo.softDeleteChecklistCalled || !txRepo.softDeleteItemsByChecklistCalled {
		t.Fatalf("expected lock + soft delete header + soft delete items")
	}
	if !bytes.Equal(txRepo.softDeleteChecklistID, id) || !bytes.Equal(txRepo.softDeleteItemsByChecklistID, id) {
		t.Fatalf("expected delete id propagated")
	}
}

func TestAirRescuerChecklistServiceDeleteChecklistBranches(t *testing.T) {
	id := []byte("1234567890123456")
	deletedBy := []byte("abcdefghijklmnop")

	t.Run("lock failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{lockChecklistByIDErr: errors.New("lock failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		err := svc.DeleteChecklist(context.Background(), id, deletedBy)
		if err == nil || err.Error() != "lock failed" {
			t.Fatalf("expected lock error, got %v", err)
		}
	})

	t.Run("soft delete header failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{softDeleteChecklistErr: errors.New("delete header failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		err := svc.DeleteChecklist(context.Background(), id, deletedBy)
		if err == nil || err.Error() != "delete header failed" {
			t.Fatalf("expected header delete error, got %v", err)
		}
	})

	t.Run("soft delete items failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{softDeleteItemsByChecklistErr: errors.New("delete items failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		err := svc.DeleteChecklist(context.Background(), id, deletedBy)
		if err == nil || err.Error() != "delete items failed" {
			t.Fatalf("expected items delete error, got %v", err)
		}
	})
}

func TestAirRescuerChecklistServiceCreateChecklistItem(t *testing.T) {
	checklistID := []byte("1234567890123456")
	txRepo := &airRescuerChecklistTxRepoMock{nextChecklistItemPosition: 6}
	repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
	svc := NewAirRescuerChecklistService(repo)

	row := &airrescuechecklist.AirRescuerChecklistItem{ChecklistID: append([]byte(nil), checklistID...), Position: 0}
	if err := svc.CreateChecklistItem(context.Background(), row); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !txRepo.lockChecklistByIDCalled || !txRepo.createChecklistItemCalled {
		t.Fatalf("expected lock + create item")
	}
	if row.Position != 6 {
		t.Fatalf("expected position auto-assigned")
	}
}

func TestAirRescuerChecklistServiceCreateChecklistItemBranches(t *testing.T) {
	t.Run("nil row", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		if err := svc.CreateChecklistItem(context.Background(), nil); err == nil {
			t.Fatalf("expected invalid data error")
		}
	})

	t.Run("lock failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{lockChecklistByIDErr: errors.New("lock failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		err := svc.CreateChecklistItem(context.Background(), &airrescuechecklist.AirRescuerChecklistItem{ChecklistID: []byte("1234567890123456")})
		if err == nil || err.Error() != "lock failed" {
			t.Fatalf("expected lock error, got %v", err)
		}
	})

	t.Run("create failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{createChecklistItemErr: errors.New("create failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		err := svc.CreateChecklistItem(context.Background(), &airrescuechecklist.AirRescuerChecklistItem{ChecklistID: []byte("1234567890123456")})
		if err == nil || err.Error() != "create failed" {
			t.Fatalf("expected create error, got %v", err)
		}
	})
}

func TestAirRescuerChecklistServiceUpdateChecklistItem(t *testing.T) {
	itemID := []byte("1234567890123456")
	txRepo := &airRescuerChecklistTxRepoMock{}
	repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
	svc := NewAirRescuerChecklistService(repo)

	row := &airrescuechecklist.AirRescuerChecklistItem{ID: append([]byte(nil), itemID...), Position: 0}
	if err := svc.UpdateChecklistItem(context.Background(), row); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !txRepo.lockChecklistItemByIDCalled || !txRepo.updateChecklistItemCalled {
		t.Fatalf("expected lock + update item")
	}
	if row.Position != 1 {
		t.Fatalf("expected position normalized")
	}
}

func TestAirRescuerChecklistServiceUpdateChecklistItemBranches(t *testing.T) {
	t.Run("nil row", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		if err := svc.UpdateChecklistItem(context.Background(), nil); err == nil {
			t.Fatalf("expected invalid data error")
		}
	})

	t.Run("lock failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{lockChecklistItemByIDErr: errors.New("lock item failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		err := svc.UpdateChecklistItem(context.Background(), &airrescuechecklist.AirRescuerChecklistItem{ID: []byte("1234567890123456")})
		if err == nil || err.Error() != "lock item failed" {
			t.Fatalf("expected lock item error, got %v", err)
		}
	})

	t.Run("update failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{updateChecklistItemErr: errors.New("update item failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		err := svc.UpdateChecklistItem(context.Background(), &airrescuechecklist.AirRescuerChecklistItem{ID: []byte("1234567890123456")})
		if err == nil || err.Error() != "update item failed" {
			t.Fatalf("expected update item error, got %v", err)
		}
	})
}

func TestAirRescuerChecklistServiceDeleteChecklistItem(t *testing.T) {
	itemID := []byte("1234567890123456")
	deletedBy := []byte("abcdefghijklmnop")
	txRepo := &airRescuerChecklistTxRepoMock{}
	repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
	svc := NewAirRescuerChecklistService(repo)

	if err := svc.DeleteChecklistItem(context.Background(), itemID, deletedBy); err != nil {
		t.Fatalf("unexpected error: %v", err)
	}
	if !txRepo.lockChecklistItemByIDCalled || !txRepo.softDeleteChecklistItemCalled {
		t.Fatalf("expected lock + soft delete item")
	}
	if !bytes.Equal(txRepo.softDeleteChecklistItemID, itemID) {
		t.Fatalf("expected deleted item id propagated")
	}
}

func TestAirRescuerChecklistServiceDeleteChecklistItemBranches(t *testing.T) {
	itemID := []byte("1234567890123456")
	deletedBy := []byte("abcdefghijklmnop")

	t.Run("lock failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{lockChecklistItemByIDErr: errors.New("lock item failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		err := svc.DeleteChecklistItem(context.Background(), itemID, deletedBy)
		if err == nil || err.Error() != "lock item failed" {
			t.Fatalf("expected lock item error, got %v", err)
		}
	})

	t.Run("delete failed", func(t *testing.T) {
		txRepo := &airRescuerChecklistTxRepoMock{softDeleteChecklistItemErr: errors.New("delete item failed")}
		repo := &airRescuerChecklistRepoMock{txRepo: txRepo}
		svc := NewAirRescuerChecklistService(repo)
		err := svc.DeleteChecklistItem(context.Background(), itemID, deletedBy)
		if err == nil || err.Error() != "delete item failed" {
			t.Fatalf("expected delete item error, got %v", err)
		}
	})
}

func TestAirRescuerChecklistServiceReadWrappers(t *testing.T) {
	checklistID := []byte("1234567890123456")
	itemID := []byte("abcdefghijklmnop")
	headers := []airrescuechecklist.ChecklistHeaderView{{Title: "A"}}
	items := []airrescuechecklist.ChecklistItemView{{Name: "I"}}
	bases := []airrescuechecklist.HEMSBaseView{{BaseName: "B"}}

	repo := &airRescuerChecklistRepoMock{
		txRepo:                      &airRescuerChecklistTxRepoMock{},
		getChecklistByIDRow:         &airrescuechecklist.ChecklistHeaderView{ID: checklistID},
		getChecklistItemByIDRow:     &airrescuechecklist.ChecklistItemView{ID: itemID},
		listChecklistHeadersRows:    headers,
		listChecklistItemsByIDsRows: items,
		listHEMSBasesRows:           bases,
	}
	svc := NewAirRescuerChecklistService(repo)

	if _, err := svc.GetChecklistByID(context.Background(), checklistID); err != nil {
		t.Fatalf("GetChecklistByID error: %v", err)
	}
	if _, err := svc.GetChecklistItemByID(context.Background(), itemID); err != nil {
		t.Fatalf("GetChecklistItemByID error: %v", err)
	}
	if _, err := svc.ListChecklistHeaders(context.Background(), airrescuechecklist.ChecklistHeaderFilter{ScopeCode: "TA"}); err != nil {
		t.Fatalf("ListChecklistHeaders error: %v", err)
	}
	if _, err := svc.ListChecklistItemsByChecklistIDs(context.Background(), [][]byte{checklistID}); err != nil {
		t.Fatalf("ListChecklistItemsByChecklistIDs error: %v", err)
	}
	if _, err := svc.ListHEMSBases(context.Background(), airrescuechecklist.HEMSBaseFilter{HEMSBaseID: checklistID}); err != nil {
		t.Fatalf("ListHEMSBases error: %v", err)
	}

	if !repo.getChecklistByIDCalled || !repo.getChecklistItemByIDCalled || !repo.listChecklistHeadersCalled || !repo.listChecklistItemsByIDsCalled || !repo.listHEMSBasesCalled {
		t.Fatalf("expected all read wrappers to delegate to repository")
	}
}
