import 'package:flutter/foundation.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

import 'package:superport_v2/core/common/models/paginated_result.dart';
import 'package:superport_v2/features/approvals/data/dtos/approval_dto.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval_draft.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval_template.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval_proceed_status.dart';
import 'package:superport_v2/features/approvals/domain/errors/approval_access_denied_exception.dart';
import 'package:superport_v2/features/approvals/domain/repositories/approval_repository.dart';
import 'package:superport_v2/features/approvals/domain/repositories/approval_template_repository.dart';
import 'package:superport_v2/features/approvals/domain/usecases/get_approval_draft_use_case.dart';
import 'package:superport_v2/features/approvals/domain/usecases/list_approval_drafts_use_case.dart';
import 'package:superport_v2/features/approvals/domain/usecases/save_approval_draft_use_case.dart';
import 'package:superport_v2/features/approvals/presentation/controllers/approval_controller.dart';
import 'package:superport_v2/features/inventory/lookups/domain/entities/lookup_item.dart';
import 'package:superport_v2/features/inventory/lookups/domain/repositories/inventory_lookup_repository.dart';

import '../../../../helpers/fixture_loader.dart';

/// ApprovalRepository 모킹 클래스.
class _MockApprovalRepository extends Mock implements ApprovalRepository {}

/// Approval 생성 요청을 대체하기 위한 가짜 입력.
class _FakeApprovalCreateInput extends Fake implements ApprovalCreateInput {}

/// Approval 수정 요청을 대체하기 위한 가짜 입력.
class _FakeApprovalUpdateInput extends Fake implements ApprovalUpdateInput {}

/// 단계 행위 요청을 대체하기 위한 가짜 입력.
class _FakeStepActionInput extends Fake implements ApprovalStepActionInput {}

/// ApprovalTemplateRepository 모킹 클래스.
class _MockApprovalTemplateRepository extends Mock
    implements ApprovalTemplateRepository {}

/// 템플릿 단계 할당 요청을 대체하기 위한 가짜 입력.
class _FakeStepAssignmentInput extends Fake
    implements ApprovalStepAssignmentInput {}

class _MockInventoryLookupRepository extends Mock
    implements InventoryLookupRepository {}

class _MockSaveApprovalDraftUseCase extends Mock
    implements SaveApprovalDraftUseCase {}

class _MockGetApprovalDraftUseCase extends Mock
    implements GetApprovalDraftUseCase {}

class _MockListApprovalDraftsUseCase extends Mock
    implements ListApprovalDraftsUseCase {}

void main() {
  late ApprovalController controller;
  late _MockApprovalRepository repository;
  late _MockApprovalTemplateRepository templateRepository;

  final sampleApproval = ApprovalDto.fromJson(
    loadJsonFixture('approvals/approval_five_step_pending.json'),
  ).toEntity();

  final sampleStep = sampleApproval.currentStep ?? sampleApproval.steps.first;

  /// 테스트용 페이징 응답을 생성하는 헬퍼.
  PaginatedResult<Approval> createResult(List<Approval> items) {
    return PaginatedResult<Approval>(
      items: items,
      page: 1,
      pageSize: 20,
      total: items.length,
    );
  }

  setUpAll(() {
    registerFallbackValue(_FakeApprovalCreateInput());
    registerFallbackValue(_FakeApprovalUpdateInput());
    registerFallbackValue(_FakeStepActionInput());
    registerFallbackValue(_FakeStepAssignmentInput());
    registerFallbackValue(const ApprovalDraftListFilter(requesterId: 0));
    registerFallbackValue(
      ApprovalDraftSaveInput(
        requesterId: 0,
        steps: [ApprovalDraftStep(stepOrder: 0, approverId: 0)],
      ),
    );
  });

  setUp(() {
    repository = _MockApprovalRepository();
    templateRepository = _MockApprovalTemplateRepository();
    controller = ApprovalController(
      approvalRepository: repository,
      templateRepository: templateRepository,
    );
    when(() => repository.canProceed(any())).thenAnswer(
      (_) async => ApprovalProceedStatus(
        approvalId: sampleApproval.id!,
        canProceed: true,
      ),
    );
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        transactionId: any(named: 'transactionId'),
        approvalStatusId: any(named: 'approvalStatusId'),
        requestedById: any(named: 'requestedById'),
        statusCodes: any(named: 'statusCodes'),
        includePending: any(named: 'includePending'),
        includeHistories: any(named: 'includeHistories'),
        includeSteps: any(named: 'includeSteps'),
      ),
    ).thenAnswer((_) async => createResult([sampleApproval]));
  });

  // fetch 메서드 관련 시나리오
  group('fetch', () {
    setUp(() {
      when(
        () => repository.list(
          page: any(named: 'page'),
          pageSize: any(named: 'pageSize'),
          transactionId: any(named: 'transactionId'),
          approvalStatusId: any(named: 'approvalStatusId'),
          requestedById: any(named: 'requestedById'),
          statusCodes: any(named: 'statusCodes'),
          includePending: any(named: 'includePending'),
          includeHistories: any(named: 'includeHistories'),
          includeSteps: any(named: 'includeSteps'),
        ),
      ).thenAnswer((_) async => createResult([sampleApproval]));
    });

    // 정상적으로 결재 목록을 조회한다.
    test('목록을 조회한다', () async {
      await controller.fetch();

      expect(controller.result?.items, isNotEmpty);
      expect(controller.errorMessage, isNull);
    });

    test('전체 상태 필터는 include_pending을 true로 전달한다', () async {
      await controller.fetch();

      final captured = verify(
        () => repository.list(
          page: 1,
          pageSize: 20,
          transactionId: null,
          approvalStatusId: null,
          requestedById: null,
          statusCodes: captureAny(named: 'statusCodes'),
          includePending: captureAny(named: 'includePending'),
          includeHistories: false,
          includeSteps: false,
        ),
      ).captured;

      final includePending = captured[0] as bool;
      final statusCodes = captured[1] as List<String>?;
      expect(includePending, isTrue);
      expect(statusCodes, isNull);
    });

    test('403 접근 거부 시 accessDenied 플래그를 노출한다', () async {
      when(
        () => repository.list(
          page: any(named: 'page'),
          pageSize: any(named: 'pageSize'),
          transactionId: any(named: 'transactionId'),
          approvalStatusId: any(named: 'approvalStatusId'),
          requestedById: any(named: 'requestedById'),
          statusCodes: any(named: 'statusCodes'),
          includePending: any(named: 'includePending'),
          includeHistories: any(named: 'includeHistories'),
          includeSteps: any(named: 'includeSteps'),
        ),
      ).thenThrow(const ApprovalAccessDeniedException(message: '테스트 접근 제한'));

      await controller.fetch();

      expect(controller.isAccessDenied, isTrue);
      expect(controller.accessDeniedMessage, '테스트 접근 제한');
      expect(controller.result, isNull);
      expect(controller.errorMessage, isNull);
    });

    // 검색어/상태/기간 필터가 Repository 호출에 반영되는지 확인한다.
    test('필터 전달을 검증한다', () async {
      controller.updateTransactionFilter(55);
      controller.updateStatusFilter(ApprovalStatusFilter.approved);
      controller.updateRequestedByFilter(
        id: 77,
        name: '상신자',
        employeeNo: 'EMP077',
      );

      await controller.fetch(page: 3);

      final captured = verify(
        () => repository.list(
          page: 3,
          pageSize: 20,
          transactionId: 55,
          approvalStatusId: null,
          requestedById: 77,
          statusCodes: captureAny(named: 'statusCodes'),
          includePending: captureAny(named: 'includePending'),
          includeHistories: false,
          includeSteps: false,
        ),
      ).captured;

      final includePending = captured[0] as bool;
      final statusCodes = captured[1] as List<String>?;
      expect(includePending, isFalse);
      expect(listEquals(statusCodes, const ['approved']), isTrue);
    });

    test('대기 상태 필터는 초안·상신 상태 코드를 전달한다', () async {
      controller.updateStatusFilter(ApprovalStatusFilter.pending);

      await controller.fetch();

      final captured = verify(
        () => repository.list(
          page: 1,
          pageSize: 20,
          transactionId: null,
          approvalStatusId: null,
          requestedById: null,
          statusCodes: captureAny(named: 'statusCodes'),
          includePending: captureAny(named: 'includePending'),
          includeHistories: false,
          includeSteps: false,
        ),
      ).captured;

      final includePending = captured[0] as bool;
      final statusCodes = captured[1] as List<String>?;
      expect(includePending, isFalse);
      expect(
        listEquals(statusCodes, const ['draft', 'submitted', 'in_progress']),
        isTrue,
      );
    });

    // Repository 오류 발생 시 errorMessage가 설정된다.
    test('에러 발생 시 errorMessage 설정', () async {
      when(
        () => repository.list(
          page: any(named: 'page'),
          pageSize: any(named: 'pageSize'),
          transactionId: any(named: 'transactionId'),
          approvalStatusId: any(named: 'approvalStatusId'),
          requestedById: any(named: 'requestedById'),
          statusCodes: any(named: 'statusCodes'),
          includePending: any(named: 'includePending'),
          includeHistories: any(named: 'includeHistories'),
          includeSteps: any(named: 'includeSteps'),
        ),
      ).thenThrow(Exception('fail'));

      await controller.fetch();

      expect(controller.errorMessage, isNotNull);
    });

    test('상태 룩업 로드 후 status ID를 전달한다', () async {
      final lookupRepository = _MockInventoryLookupRepository();
      final ctrl = ApprovalController(
        approvalRepository: repository,
        templateRepository: templateRepository,
        lookupRepository: lookupRepository,
      );
      when(
        () => lookupRepository.fetchApprovalStatuses(
          activeOnly: any(named: 'activeOnly'),
        ),
      ).thenAnswer(
        (_) async => [
          LookupItem(
            id: 5,
            name: '승인완료',
            code: 'approved',
            isDefault: false,
            isActive: true,
          ),
        ],
      );

      await ctrl.loadStatusLookups();
      ctrl.updateStatusFilter(ApprovalStatusFilter.approved);
      await ctrl.fetch();

      final captured = verify(
        () => repository.list(
          page: 1,
          pageSize: 20,
          transactionId: null,
          approvalStatusId: 5,
          requestedById: null,
          statusCodes: captureAny(named: 'statusCodes'),
          includePending: captureAny(named: 'includePending'),
          includeHistories: false,
          includeSteps: false,
        ),
      ).captured;

      final includePending = captured[0] as bool;
      final statusCodes = captured[1] as List<String>?;
      expect(includePending, isFalse);
      expect(listEquals(statusCodes, const ['approved']), isTrue);
    });
  });

  group('selectApproval', () {
    test('상세를 조회하고 저장한다', () async {
      when(
        () => repository.fetchDetail(
          any(),
          includeSteps: any(named: 'includeSteps'),
          includeHistories: any(named: 'includeHistories'),
        ),
      ).thenAnswer((_) async => sampleApproval);

      await controller.selectApproval(sampleApproval.id!);

      expect(controller.selected, isNotNull);
      verify(
        () => repository.fetchDetail(
          sampleApproval.id!,
          includeSteps: true,
          includeHistories: true,
        ),
      ).called(1);
      verify(() => repository.canProceed(sampleApproval.id!)).called(1);
      expect(controller.canProceedSelected, isTrue);
    });

    test('상세 조회에서 접근 거부 시 accessDenied를 기록한다', () async {
      when(
        () => repository.fetchDetail(
          any(),
          includeSteps: any(named: 'includeSteps'),
          includeHistories: any(named: 'includeHistories'),
        ),
      ).thenThrow(const ApprovalAccessDeniedException(message: '상세 접근 제한'));

      await controller.selectApproval(sampleApproval.id!);

      expect(controller.isAccessDenied, isTrue);
      expect(controller.accessDeniedMessage, '상세 접근 제한');
      expect(controller.selected, isNull);
      verifyNever(() => repository.canProceed(any()));
    });

    test('에러 발생 시 errorMessage 설정', () async {
      when(
        () => repository.fetchDetail(
          any(),
          includeSteps: any(named: 'includeSteps'),
          includeHistories: any(named: 'includeHistories'),
        ),
      ).thenThrow(Exception('detail fail'));

      await controller.selectApproval(sampleApproval.id!);

      expect(controller.errorMessage, isNotNull);
    });
  });

  group('loadActionOptions', () {
    test('행위 목록을 불러오고 캐시한다', () async {
      when(
        () => repository.listActions(activeOnly: any(named: 'activeOnly')),
      ).thenAnswer((_) async => [ApprovalAction(id: 1, name: 'approve')]);

      await controller.loadActionOptions(force: true);

      expect(controller.hasActionOptions, isTrue);
      expect(controller.actionOptions.length, 1);

      clearInteractions(repository);

      await controller.loadActionOptions();

      verifyNever(
        () => repository.listActions(activeOnly: any(named: 'activeOnly')),
      );
    });

    test('에러 발생 시 errorMessage 설정', () async {
      when(
        () => repository.listActions(activeOnly: any(named: 'activeOnly')),
      ).thenThrow(Exception('actions fail'));

      await controller.loadActionOptions(force: true);

      expect(controller.errorMessage, isNotNull);
    });
  });

  group('loadTemplates', () {
    test('템플릿 목록을 불러오고 캐시한다', () async {
      when(
        () => templateRepository.list(
          page: any(named: 'page'),
          pageSize: any(named: 'pageSize'),
          query: any(named: 'query'),
          isActive: any(named: 'isActive'),
        ),
      ).thenAnswer(
        (_) async => PaginatedResult<ApprovalTemplate>(
          items: [
            ApprovalTemplate(
              id: 1,
              code: 'TEMP',
              name: '기본 템플릿',
              isActive: true,
              steps: const [],
            ),
          ],
          page: 1,
          pageSize: 20,
          total: 1,
        ),
      );

      await controller.loadTemplates(force: true);

      expect(controller.templates.length, 1);
      expect(controller.isLoadingTemplates, isFalse);

      clearInteractions(templateRepository);

      await controller.loadTemplates();

      verifyNever(
        () => templateRepository.list(
          page: 1,
          pageSize: 100,
          query: null,
          isActive: true,
        ),
      );
    });

    test('에러 발생 시 errorMessage 설정', () async {
      when(
        () => templateRepository.list(
          page: any(named: 'page'),
          pageSize: any(named: 'pageSize'),
          query: any(named: 'query'),
          isActive: any(named: 'isActive'),
        ),
      ).thenThrow(Exception('template fail'));

      await controller.loadTemplates(force: true);

      expect(controller.errorMessage, isNotNull);
      expect(controller.isLoadingTemplates, isFalse);
    });
  });

  group('performStepAction', () {
    late ApprovalStep updatedStep;
    late Approval updatedApproval;

    setUp(() {
      when(
        () => repository.listActions(activeOnly: any(named: 'activeOnly')),
      ).thenAnswer(
        (_) async => [
          ApprovalAction(id: 1, name: 'approve'),
          ApprovalAction(id: 2, name: 'reject'),
          ApprovalAction(id: 3, name: 'comment'),
        ],
      );

      when(
        () => repository.list(
          page: any(named: 'page'),
          pageSize: any(named: 'pageSize'),
          transactionId: any(named: 'transactionId'),
          approvalStatusId: any(named: 'approvalStatusId'),
          requestedById: any(named: 'requestedById'),
          includeHistories: any(named: 'includeHistories'),
          includeSteps: any(named: 'includeSteps'),
        ),
      ).thenAnswer((_) async => createResult([sampleApproval]));

      when(
        () => repository.fetchDetail(
          any(),
          includeSteps: any(named: 'includeSteps'),
          includeHistories: any(named: 'includeHistories'),
        ),
      ).thenAnswer((_) async => sampleApproval);

      updatedStep = ApprovalStep(
        id: 11,
        stepOrder: 1,
        approver: sampleStep.approver,
        status: ApprovalStatus(id: 2, name: '승인'),
        assignedAt: sampleStep.assignedAt,
        decidedAt: DateTime(2024, 4, 1, 9, 30),
        note: '승인 완료',
      );

      updatedApproval = sampleApproval.copyWith(
        status: ApprovalStatus(id: 2, name: '승인'),
        currentStep: updatedStep,
        steps: [updatedStep],
      );
    });

    test('성공 시 상세와 목록을 갱신한다', () async {
      when(
        () => repository.performStepAction(any()),
      ).thenAnswer((_) async => updatedApproval);

      await controller.loadActionOptions(force: true);
      await controller.fetch();
      expect(controller.result, isNotNull);
      await controller.selectApproval(sampleApproval.id!);

      final success = await controller.performStepAction(
        step: sampleStep,
        type: ApprovalStepActionType.approve,
      );

      expect(success, isTrue);
      expect(controller.selected?.status.id, updatedApproval.status.id);
      expect(controller.result, isNotNull);
      expect(
        controller.result!.items.first.status.id,
        updatedApproval.status.id,
      );
      expect(controller.isPerformingAction, isFalse);
      verify(() => repository.performStepAction(any())).called(1);
    });

    test('예외 발생 시 errorMessage 설정', () async {
      when(
        () => repository.performStepAction(any()),
      ).thenThrow(Exception('action fail'));

      await controller.loadActionOptions(force: true);
      await controller.fetch();
      await controller.selectApproval(sampleApproval.id!);

      final success = await controller.performStepAction(
        step: sampleStep,
        type: ApprovalStepActionType.approve,
      );

      expect(success, isFalse);
      expect(controller.errorMessage, isNotNull);
      expect(controller.isPerformingAction, isFalse);
    });

    test('canProceed가 false면 액션을 중단한다', () async {
      when(() => repository.canProceed(any())).thenAnswer(
        (_) async => ApprovalProceedStatus(
          approvalId: sampleApproval.id!,
          canProceed: false,
          reason: '선행 단계가 완료되지 않았습니다.',
        ),
      );

      await controller.loadActionOptions(force: true);
      await controller.fetch();
      await controller.selectApproval(sampleApproval.id!);

      final success = await controller.performStepAction(
        step: sampleStep,
        type: ApprovalStepActionType.approve,
      );

      expect(success, isFalse);
      expect(controller.errorMessage, contains('선행 단계'));
      expect(controller.canProceedSelected, isFalse);
      verifyNever(() => repository.performStepAction(any()));
    });

    test('행위를 찾지 못하면 요청하지 않는다', () async {
      when(
        () => repository.listActions(activeOnly: any(named: 'activeOnly')),
      ).thenAnswer((_) async => [ApprovalAction(id: 9, name: 'other')]);

      await controller.loadActionOptions(force: true);
      await controller.fetch();
      await controller.selectApproval(sampleApproval.id!);

      final success = await controller.performStepAction(
        step: sampleStep,
        type: ApprovalStepActionType.approve,
      );

      expect(success, isFalse);
      expect(controller.errorMessage, isNotNull);
      verifyNever(() => repository.performStepAction(any()));
    });
  });

  group('applyTemplate', () {
    late ApprovalTemplate template;
    late Approval updatedApproval;

    setUp(() {
      template = ApprovalTemplate(
        id: 901,
        code: 'TEMP-001',
        name: '입고 1단계',
        isActive: true,
        steps: [
          ApprovalTemplateStep(
            stepOrder: 1,
            approver: ApprovalTemplateApprover(
              id: 21,
              employeeNo: 'E001',
              name: '최승인',
            ),
          ),
        ],
      );

      when(
        () => templateRepository.list(
          page: any(named: 'page'),
          pageSize: any(named: 'pageSize'),
          query: any(named: 'query'),
          isActive: any(named: 'isActive'),
        ),
      ).thenAnswer(
        (_) async => PaginatedResult<ApprovalTemplate>(
          items: [template],
          page: 1,
          pageSize: 20,
          total: 1,
        ),
      );

      when(
        () => templateRepository.fetchDetail(
          any(),
          includeSteps: any(named: 'includeSteps'),
        ),
      ).thenAnswer((_) async => template);

      when(
        () => repository.list(
          page: any(named: 'page'),
          pageSize: any(named: 'pageSize'),
          transactionId: any(named: 'transactionId'),
          approvalStatusId: any(named: 'approvalStatusId'),
          requestedById: any(named: 'requestedById'),
          includeHistories: any(named: 'includeHistories'),
          includeSteps: any(named: 'includeSteps'),
        ),
      ).thenAnswer((_) async => createResult([sampleApproval]));

      when(
        () => repository.fetchDetail(
          any(),
          includeSteps: any(named: 'includeSteps'),
          includeHistories: any(named: 'includeHistories'),
        ),
      ).thenAnswer((_) async => sampleApproval);

      updatedApproval = sampleApproval.copyWith(
        steps: [
          ApprovalStep(
            id: 11,
            stepOrder: 1,
            approver: ApprovalApprover(id: 21, employeeNo: 'E001', name: '최승인'),
            status: ApprovalStatus(id: 1, name: '대기'),
            assignedAt: DateTime(2024, 4, 1, 9),
          ),
        ],
      );
    });

    test('성공 시 결재 상세를 갱신한다', () async {
      when(
        () => repository.assignSteps(any()),
      ).thenAnswer((_) async => updatedApproval);

      await controller.loadTemplates(force: true);
      await controller.fetch();
      await controller.selectApproval(sampleApproval.id!);

      final success = await controller.applyTemplate(template.id);

      expect(success, isTrue);
      expect(controller.selected?.steps.length, 1);
      verify(() => repository.assignSteps(any())).called(1);
    });

    test('템플릿 단계가 없으면 실패한다', () async {
      when(
        () => templateRepository.fetchDetail(
          any(),
          includeSteps: any(named: 'includeSteps'),
        ),
      ).thenAnswer(
        (_) async => ApprovalTemplate(
          id: template.id,
          code: template.code,
          name: template.name,
          description: template.description,
          isActive: template.isActive,
          createdBy: template.createdBy,
          createdAt: template.createdAt,
          updatedAt: template.updatedAt,
          steps: const [],
        ),
      );

      await controller.selectApproval(sampleApproval.id!);

      final success = await controller.applyTemplate(template.id);

      expect(success, isFalse);
      expect(controller.errorMessage, isNotNull);
    });

    test('예외 발생 시 errorMessage 설정', () async {
      when(
        () => templateRepository.fetchDetail(
          any(),
          includeSteps: any(named: 'includeSteps'),
        ),
      ).thenThrow(Exception('template detail fail'));

      await controller.selectApproval(sampleApproval.id!);

      final success = await controller.applyTemplate(template.id);

      expect(success, isFalse);
      expect(controller.errorMessage, isNotNull);
    });

    test('선택된 결재가 없으면 적용하지 않는다', () async {
      final success = await controller.applyTemplate(template.id);

      expect(success, isFalse);
      expect(controller.errorMessage, isNotNull);
      verifyNever(() => repository.assignSteps(any()));
    });
  });

  test('cacheSubmissionDraft와 consumeSubmissionDraft가 초안을 관리한다', () {
    final draft = ApprovalSubmissionInput(
      statusId: 1,
      requesterId: 5,
      steps: const [],
    );

    controller.cacheSubmissionDraft(draft);

    expect(controller.hasSubmissionDraft, isTrue);
    expect(controller.submissionDraft, same(draft));

    final restored = controller.consumeSubmissionDraft();

    expect(restored, same(draft));
    expect(controller.hasSubmissionDraft, isFalse);
  });

  test('clearSubmissionDraft는 저장된 초안을 제거한다', () {
    controller.cacheSubmissionDraft(
      ApprovalSubmissionInput(statusId: 1, requesterId: 2, steps: const []),
    );

    controller.clearSubmissionDraft();

    expect(controller.submissionDraft, isNull);
    expect(controller.hasSubmissionDraft, isFalse);
  });

  test('cacheSubmissionDraft는 서버 초안을 저장한다', () async {
    final saveUseCase = _MockSaveApprovalDraftUseCase();
    final detail = ApprovalDraftDetail(
      id: 99,
      requesterId: 5,
      savedAt: DateTime.utc(2025, 1, 1),
      payload: ApprovalDraftPayload(steps: const []),
    );
    when(() => saveUseCase.call(any())).thenAnswer((_) async => detail);

    final controllerWithSave = ApprovalController(
      approvalRepository: repository,
      templateRepository: templateRepository,
      saveDraftUseCase: saveUseCase,
    );

    final draft = ApprovalSubmissionInput(
      statusId: 3,
      requesterId: 5,
      transactionId: 10,
      steps: [ApprovalStepAssignmentItem(stepOrder: 1, approverId: 7)],
    );

    controllerWithSave.cacheSubmissionDraft(draft);
    await Future<void>.delayed(Duration.zero);

    final captured = verify(() => saveUseCase.call(captureAny())).captured;
    expect(captured, isNotEmpty);
    final input = captured.first as ApprovalDraftSaveInput;
    expect(input.requesterId, 5);
    expect(input.transactionId, 10);
    expect(input.statusId, 3);
    expect(input.sessionKey, 'approval_submission_5');
  });

  test('restoreSubmissionDraft는 서버 초안을 불러온다', () async {
    final listUseCase = _MockListApprovalDraftsUseCase();
    final getUseCase = _MockGetApprovalDraftUseCase();
    final summary = ApprovalDraftSummary(
      id: 77,
      requesterId: 5,
      status: ApprovalDraftStatus.active,
      savedAt: DateTime.utc(2025, 1, 3),
      sessionKey: 'approval_submission_5',
      stepCount: 1,
    );
    when(() => listUseCase.call(any())).thenAnswer(
      (_) async => PaginatedResult<ApprovalDraftSummary>(
        items: [summary],
        page: 1,
        pageSize: 10,
        total: 1,
      ),
    );
    final detail = ApprovalDraftDetail(
      id: summary.id,
      requesterId: 5,
      savedAt: DateTime.utc(2025, 1, 3),
      payload: ApprovalDraftPayload(
        templateId: 8,
        metadata: const {
          '_client_state': {'status_id': 6},
        },
        steps: [ApprovalDraftStep(stepOrder: 1, approverId: 9)],
      ),
    );
    when(
      () => getUseCase.call(id: summary.id, requesterId: 5),
    ).thenAnswer((_) async => detail);

    final controllerWithDrafts = ApprovalController(
      approvalRepository: repository,
      templateRepository: templateRepository,
      listDraftsUseCase: listUseCase,
      getDraftUseCase: getUseCase,
    );

    final restored = await controllerWithDrafts.restoreSubmissionDraft(
      requesterId: 5,
    );

    expect(restored, isNotNull);
    expect(restored!.statusId, 6);
    expect(restored.steps, hasLength(1));
    expect(controllerWithDrafts.submissionDraft, isNotNull);

    verify(() => listUseCase.call(any())).called(1);
    verify(() => getUseCase.call(id: summary.id, requesterId: 5)).called(1);
  });

  test('statusLabel은 draft 라벨을 반환한다', () {
    final label = controller.statusLabel(ApprovalStatusFilter.draft);

    expect(label, '임시저장');
  });

  test('목록에서 사라진 결재는 선택 상태를 해제한다', () async {
    when(
      () => repository.fetchDetail(
        any(),
        includeSteps: any(named: 'includeSteps'),
        includeHistories: any(named: 'includeHistories'),
      ),
    ).thenAnswer((_) async => sampleApproval);

    await controller.fetch();
    await controller.selectApproval(sampleApproval.id!);
    expect(controller.selected, isNotNull);
    expect(controller.proceedStatus, isNotNull);

    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        transactionId: any(named: 'transactionId'),
        approvalStatusId: any(named: 'approvalStatusId'),
        requestedById: any(named: 'requestedById'),
        statusCodes: any(named: 'statusCodes'),
        includePending: any(named: 'includePending'),
        includeHistories: any(named: 'includeHistories'),
        includeSteps: any(named: 'includeSteps'),
      ),
    ).thenAnswer((_) async => createResult(const []));

    await controller.fetch();

    expect(controller.selected, isNull);
    expect(controller.proceedStatus, isNull);
  });

  test('필터 초기화', () {
    controller.updateTransactionFilter(42);
    controller.updateStatusFilter(ApprovalStatusFilter.rejected);
    controller.updateRequestedByFilter(
      id: 11,
      name: '요청자',
      employeeNo: 'EMP011',
    );

    controller.clearFilters();

    expect(controller.transactionIdFilter, isNull);
    expect(controller.statusFilter, ApprovalStatusFilter.all);
    expect(controller.requestedById, isNull);
    expect(controller.requestedByName, isNull);
  });
}
