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/core/network/failure.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval_flow.dart';
import 'package:superport_v2/features/approvals/domain/repositories/approval_repository.dart';
import 'package:superport_v2/features/approvals/domain/usecases/recall_approval_use_case.dart';
import 'package:superport_v2/features/approvals/domain/usecases/resubmit_approval_use_case.dart';
import 'package:superport_v2/features/approvals/history/domain/entities/approval_history_record.dart';
import 'package:superport_v2/features/approvals/history/domain/repositories/approval_history_repository.dart';
import 'package:superport_v2/features/approvals/history/presentation/controllers/approval_history_controller.dart';

class _MockApprovalHistoryRepository extends Mock
    implements ApprovalHistoryRepository {}

class _MockApprovalRepository extends Mock implements ApprovalRepository {}

class _MockRecallApprovalUseCase extends Mock
    implements RecallApprovalUseCase {}

class _MockResubmitApprovalUseCase extends Mock
    implements ResubmitApprovalUseCase {}

void main() {
  late ApprovalHistoryController controller;
  late _MockApprovalHistoryRepository repository;
  late _MockApprovalRepository approvalRepository;
  late _MockRecallApprovalUseCase recallUseCase;
  late _MockResubmitApprovalUseCase resubmitUseCase;

  final record = ApprovalHistoryRecord(
    id: 1,
    approvalId: 10,
    approvalNo: 'APP-2024-0001',
    stepOrder: 1,
    action: ApprovalAction(id: 11, name: 'approve', code: 'approve'),
    fromStatus: ApprovalStatus(id: 1, name: '대기', color: null),
    toStatus: ApprovalStatus(id: 2, name: '승인', color: null),
    approver: ApprovalApprover(id: 21, employeeNo: 'E001', name: '최승인'),
    actionAt: DateTime(2024, 4, 1, 9, 30),
    note: '승인 완료',
  );

  final auditEntry = ApprovalHistory(
    id: 2,
    action: ApprovalAction(id: 12, name: 'recall', code: 'recall'),
    fromStatus: ApprovalStatus(id: 2, name: '승인'),
    toStatus: ApprovalStatus(id: 3, name: '회수'),
    approver: ApprovalApprover(id: 22, employeeNo: 'E002', name: '박회수'),
    actionAt: DateTime(2024, 4, 2, 10),
    note: '요청자 회수',
  );

  PaginatedResult<ApprovalHistoryRecord> createResult(
    List<ApprovalHistoryRecord> items,
  ) {
    return PaginatedResult<ApprovalHistoryRecord>(
      items: items,
      page: 1,
      pageSize: 20,
      total: items.length,
    );
  }

  PaginatedResult<ApprovalHistory> createAuditResult(
    List<ApprovalHistory> items,
  ) {
    return PaginatedResult<ApprovalHistory>(
      items: items,
      page: 1,
      pageSize: 10,
      total: items.length,
    );
  }

  ApprovalFlow createFlow(int approvalId) {
    final status = ApprovalStatus(id: 5, name: '대기');
    final requester = ApprovalRequester(
      id: 88,
      employeeNo: 'EMP088',
      name: '상신자',
    );
    final approval = Approval(
      id: approvalId,
      approvalNo: 'APP-$approvalId',
      transactionId: approvalId * 1000,
      transactionNo: 'TRX-$approvalId',
      status: status,
      requester: requester,
      requestedAt: DateTime(2024, 4, 1),
      transactionUpdatedAt: DateTime(2024, 4, 1, 12),
      steps: const [],
    );
    return ApprovalFlow(approval: approval);
  }

  setUp(() {
    repository = _MockApprovalHistoryRepository();
    approvalRepository = _MockApprovalRepository();
    recallUseCase = _MockRecallApprovalUseCase();
    resubmitUseCase = _MockResubmitApprovalUseCase();
    when(
      () =>
          approvalRepository.listActions(activeOnly: any(named: 'activeOnly')),
    ).thenAnswer(
      (_) async => [
        ApprovalAction(id: 11, name: 'approve', code: 'approve'),
        ApprovalAction(id: 12, name: 'reject', code: 'reject'),
        ApprovalAction(id: 13, name: 'comment', code: 'comment'),
      ],
    );
    controller = ApprovalHistoryController(
      repository: repository,
      approvalRepository: approvalRepository,
      recallUseCase: recallUseCase,
      resubmitUseCase: resubmitUseCase,
    );
  });

  test('fetch 성공 시 결과를 갱신한다', () async {
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        approvalActionId: any(named: 'approvalActionId'),
        from: any(named: 'from'),
        to: any(named: 'to'),
      ),
    ).thenAnswer((_) async => createResult([record]));

    await controller.fetch();

    expect(controller.result?.items, isNotEmpty);
    expect(controller.errorMessage, isNull);
    verify(
      () => repository.list(
        page: 1,
        pageSize: 20,
        query: null,
        approvalActionId: null,
        from: null,
        to: null,
      ),
    ).called(1);
  });

  test('필터 적용 시 파라미터를 전달한다', () async {
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        approvalActionId: any(named: 'approvalActionId'),
        from: any(named: 'from'),
        to: any(named: 'to'),
      ),
    ).thenAnswer((_) async => createResult([record]));

    controller.updateQuery('APP');
    controller.updateActionFilter(ApprovalHistoryActionFilter.approve);
    controller.updateDateRange(DateTime(2024, 4, 1), DateTime(2024, 4, 30));

    await controller.fetch(page: 2);

    verify(
      () => repository.list(
        page: 2,
        pageSize: 20,
        query: 'APP',
        approvalActionId: 11,
        from: DateTime(2024, 4, 1),
        to: DateTime(2024, 4, 30),
      ),
    ).called(1);
  });

  test('에러 발생 시 errorMessage에 저장한다', () async {
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        approvalActionId: any(named: 'approvalActionId'),
        from: any(named: 'from'),
        to: any(named: 'to'),
      ),
    ).thenThrow(Exception('fail'));

    await controller.fetch();

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

  test('clearFilters가 상태를 초기화한다', () {
    controller.updateQuery('APP');
    controller.updateActionFilter(ApprovalHistoryActionFilter.comment);
    controller.updateDateRange(DateTime(2024, 4, 1), DateTime(2024, 4, 5));

    controller.clearFilters();

    expect(controller.query, isEmpty);
    expect(controller.actionFilter, ApprovalHistoryActionFilter.all);
    expect(controller.from, isNull);
    expect(controller.to, isNull);
  });

  test('fetchAuditLogs는 ApprovalRepository를 사용한다', () async {
    when(
      () => approvalRepository.listHistory(
        approvalId: any(named: 'approvalId'),
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        from: any(named: 'from'),
        to: any(named: 'to'),
        actorId: any(named: 'actorId'),
        approvalActionId: any(named: 'approvalActionId'),
      ),
    ).thenAnswer((_) async => createAuditResult([auditEntry]));

    await controller.fetchAuditLogs(approvalId: 10);

    expect(controller.auditResult?.items, isNotEmpty);
    expect(controller.selectedApprovalId, 10);
    expect(controller.auditPageSize, 10);
  });

  test('updateActiveTab이 탭 상태를 변경한다', () {
    expect(controller.activeTab, ApprovalHistoryTab.flow);
    controller.updateActiveTab(ApprovalHistoryTab.audit);
    expect(controller.activeTab, ApprovalHistoryTab.audit);
  });

  test('clearAuditSelection은 감사 로그 상태를 초기화한다', () async {
    when(
      () => approvalRepository.listHistory(
        approvalId: any(named: 'approvalId'),
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        from: any(named: 'from'),
        to: any(named: 'to'),
        actorId: any(named: 'actorId'),
        approvalActionId: any(named: 'approvalActionId'),
      ),
    ).thenAnswer((_) async => createAuditResult([auditEntry]));

    await controller.fetchAuditLogs(approvalId: 10);
    controller.clearAuditSelection();

    expect(controller.selectedApprovalId, isNull);
    expect(controller.auditResult, isNull);
  });

  test('recallApproval은 유즈케이스를 호출하고 목록을 새로고침한다', () async {
    final recallInput = ApprovalRecallInput(approvalId: 10, actorId: 77);
    when(
      () => recallUseCase.call(recallInput),
    ).thenAnswer((_) async => createFlow(10));
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        approvalActionId: any(named: 'approvalActionId'),
        from: any(named: 'from'),
        to: any(named: 'to'),
      ),
    ).thenAnswer((_) async => createResult([record]));
    when(
      () => approvalRepository.listHistory(
        approvalId: any(named: 'approvalId'),
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        from: any(named: 'from'),
        to: any(named: 'to'),
        actorId: any(named: 'actorId'),
        approvalActionId: any(named: 'approvalActionId'),
      ),
    ).thenAnswer((_) async => createAuditResult([auditEntry]));

    await controller.fetchAuditLogs(approvalId: 10);
    final flow = await controller.recallApproval(recallInput);

    expect(flow, isNotNull);
    expect(controller.isPerformingAction, isFalse);
    verify(() => recallUseCase.call(recallInput)).called(1);
    verify(
      () => repository.list(
        page: 1,
        pageSize: 20,
        query: null,
        approvalActionId: null,
        from: null,
        to: null,
      ),
    ).called(greaterThanOrEqualTo(1));
    verify(
      () => approvalRepository.listHistory(
        approvalId: 10,
        page: 1,
        pageSize: any(named: 'pageSize'),
        from: any(named: 'from'),
        to: any(named: 'to'),
        actorId: null,
        approvalActionId: null,
      ),
    ).called(greaterThanOrEqualTo(1));
  });

  test('resubmitApproval은 유즈케이스를 호출한다', () async {
    final submission = ApprovalSubmissionInput(
      statusId: 3,
      requesterId: 77,
      steps: const [],
    );
    final resubmitInput = ApprovalResubmissionInput(
      approvalId: 11,
      actorId: 77,
      submission: submission,
    );
    when(
      () => resubmitUseCase.call(resubmitInput),
    ).thenAnswer((_) async => createFlow(11));
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        approvalActionId: any(named: 'approvalActionId'),
        from: any(named: 'from'),
        to: any(named: 'to'),
      ),
    ).thenAnswer((_) async => createResult([record]));
    when(
      () => approvalRepository.listHistory(
        approvalId: any(named: 'approvalId'),
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        from: any(named: 'from'),
        to: any(named: 'to'),
        actorId: any(named: 'actorId'),
        approvalActionId: any(named: 'approvalActionId'),
      ),
    ).thenAnswer((_) async => createAuditResult([auditEntry]));

    await controller.fetchAuditLogs(approvalId: 11);
    final flow = await controller.resubmitApproval(resubmitInput);

    expect(flow, isNotNull);
    expect(controller.isPerformingAction, isFalse);
    verify(() => resubmitUseCase.call(resubmitInput)).called(1);
  });

  test('loadApprovalFlow는 403 응답 시 선택을 금지한다', () async {
    when(
      () => approvalRepository.fetchDetail(
        any(),
        includeSteps: any(named: 'includeSteps'),
        includeHistories: any(named: 'includeHistories'),
      ),
    ).thenThrow(const Failure(message: '접근이 거부되었습니다.', statusCode: 403));

    await controller.loadApprovalFlow(77);

    expect(controller.isSelectionForbidden, isTrue);
    expect(controller.selectedFlow, isNull);
    expect(controller.errorMessage, contains('접근이 거부'));
  });

  test('fetchAuditLogs는 403 응답 시 선택을 차단한다', () async {
    when(
      () => approvalRepository.listHistory(
        approvalId: any(named: 'approvalId'),
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        from: any(named: 'from'),
        to: any(named: 'to'),
        actorId: any(named: 'actorId'),
        approvalActionId: any(named: 'approvalActionId'),
      ),
    ).thenThrow(const Failure(message: '접근 불가', statusCode: 403));

    await controller.fetchAuditLogs(approvalId: 12);

    expect(controller.isSelectionForbidden, isTrue);
    expect(controller.auditResult, isNull);
    expect(controller.errorMessage, contains('접근 불가'));
  });
}
