import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:mocktail/mocktail.dart';
import 'package:shadcn_ui/shadcn_ui.dart';

import 'package:superport_v2/core/common/models/paginated_result.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';
import 'package:superport_v2/features/approvals/history/presentation/pages/approval_history_page.dart';
import 'package:superport_v2/features/auth/application/auth_service.dart';
import 'package:superport_v2/features/auth/domain/entities/auth_permission.dart';
import 'package:superport_v2/features/auth/domain/entities/auth_session.dart';
import 'package:superport_v2/features/auth/domain/entities/authenticated_user.dart';
import 'package:superport_v2/widgets/components/superport_table.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 {}

class _MockAuthService extends Mock implements AuthService {}

Widget _buildApp(Widget child) {
  return MaterialApp(
    home: ShadTheme(
      data: ShadThemeData(
        colorScheme: const ShadSlateColorScheme.light(),
        brightness: Brightness.light,
      ),
      child: Scaffold(body: child),
    ),
  );
}

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  late _MockApprovalHistoryRepository historyRepository;
  late _MockApprovalRepository approvalRepository;
  late _MockRecallApprovalUseCase recallUseCase;
  late _MockResubmitApprovalUseCase resubmitUseCase;
  late _MockAuthService authService;

  setUpAll(() {
    registerFallbackValue(ApprovalRecallInput(approvalId: 0, actorId: 0));
    registerFallbackValue(
      ApprovalResubmissionInput(
        approvalId: 0,
        actorId: 0,
        submission: ApprovalSubmissionInput(
          statusId: 0,
          requesterId: 0,
          steps: const [],
        ),
      ),
    );
  });

  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 secondRecord = ApprovalHistoryRecord(
    id: 2,
    approvalId: 11,
    approvalNo: 'APP-2024-0002',
    stepOrder: 2,
    action: ApprovalAction(id: 12, name: 'submit', code: 'submit'),
    fromStatus: ApprovalStatus(id: 1, name: '대기', color: null),
    toStatus: ApprovalStatus(id: 3, name: '진행중', color: null),
    approver: ApprovalApprover(id: 31, employeeNo: 'E031', name: '초기승인'),
    actionAt: DateTime(2024, 4, 2, 9, 30),
    note: '상신 완료',
  );

  ApprovalFlow stubFlow() {
    final approval = Approval(
      transactionId: 1,
      approvalNo: 'APP-2024-0001',
      transactionNo: 'TRX-001',
      status: ApprovalStatus(id: 2, name: '승인'),
      requester: ApprovalRequester(id: 99, employeeNo: 'E099', name: '테스터'),
      requestedAt: DateTime(2024, 4, 1),
      steps: const [],
      histories: const [],
      transactionUpdatedAt: DateTime(2024, 4, 1, 12),
    );
    return ApprovalFlow(approval: approval);
  }

  ApprovalFlow recallableFlow() {
    final status = ApprovalStatus(id: 2, name: '진행중', isTerminal: false);
    final approver = ApprovalApprover(id: 21, employeeNo: 'E001', name: '최승인');
    final steps = [
      ApprovalStep(
        id: 100,
        stepOrder: 1,
        approver: approver,
        status: status,
        assignedAt: DateTime(2024, 4, 1, 9),
        decidedAt: null,
      ),
      ApprovalStep(
        id: 101,
        stepOrder: 2,
        approver: approver,
        status: status,
        assignedAt: DateTime(2024, 4, 1, 10),
        decidedAt: DateTime(2024, 4, 1, 10, 30),
      ),
    ];
    final approval = Approval(
      id: 10,
      approvalNo: 'APP-2024-0001',
      transactionId: 10,
      transactionNo: 'TRX-001',
      status: status,
      requester: ApprovalRequester(id: 99, employeeNo: 'E099', name: '테스터'),
      requestedAt: DateTime(2024, 4, 1),
      steps: steps,
      histories: const [],
      updatedAt: DateTime(2024, 4, 1, 12),
      transactionUpdatedAt: DateTime(2024, 4, 1, 11, 50),
    );
    return ApprovalFlow(approval: approval);
  }

  // ignore: unused_element
  ApprovalFlow resubmittableFlow() {
    final pendingStatus = ApprovalStatus(id: 2, name: '대기', isTerminal: false);
    final rejectedStatus = ApprovalStatus(id: 5, name: '반려', isTerminal: true);
    final firstApprover = ApprovalApprover(
      id: 31,
      employeeNo: 'E002',
      name: '1차 승인자',
    );
    final finalApprover = ApprovalApprover(
      id: 32,
      employeeNo: 'E003',
      name: '최종 승인자',
    );
    final steps = [
      ApprovalStep(
        id: 120,
        stepOrder: 1,
        approver: firstApprover,
        status: pendingStatus,
        assignedAt: DateTime(2024, 3, 30, 9),
        decidedAt: DateTime(2024, 3, 30, 10),
      ),
      ApprovalStep(
        id: 121,
        stepOrder: 2,
        approver: finalApprover,
        status: rejectedStatus,
        assignedAt: DateTime(2024, 3, 30, 11),
        decidedAt: DateTime(2024, 3, 30, 12),
        note: '보완 필요',
      ),
    ];
    final approval = Approval(
      id: 10,
      approvalNo: 'APP-2024-0001',
      transactionId: 11,
      transactionNo: 'TRX-001',
      status: rejectedStatus,
      requester: ApprovalRequester(id: 99, employeeNo: 'E099', name: '테스터'),
      requestedAt: DateTime(2024, 3, 30),
      decidedAt: DateTime(2024, 3, 30, 12, 30),
      note: '반려 사유 공유',
      steps: steps,
      histories: const [],
      updatedAt: DateTime(2024, 3, 30, 12, 30),
      transactionUpdatedAt: DateTime(2024, 3, 30, 12),
    );
    return ApprovalFlow(approval: approval);
  }

  setUp(() {
    historyRepository = _MockApprovalHistoryRepository();
    approvalRepository = _MockApprovalRepository();
    recallUseCase = _MockRecallApprovalUseCase();
    resubmitUseCase = _MockResubmitApprovalUseCase();
    authService = _MockAuthService();

    final sl = GetIt.I;
    sl.registerLazySingleton<ApprovalHistoryRepository>(
      () => historyRepository,
    );
    sl.registerLazySingleton<ApprovalRepository>(() => approvalRepository);
    sl.registerLazySingleton<RecallApprovalUseCase>(() => recallUseCase);
    sl.registerLazySingleton<ResubmitApprovalUseCase>(() => resubmitUseCase);
    sl.registerLazySingleton<AuthService>(() => authService);

    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'),
      ],
    );

    when(
      () => approvalRepository.fetchDetail(
        any(),
        includeSteps: any(named: 'includeSteps'),
        includeHistories: any(named: 'includeHistories'),
      ),
    ).thenAnswer((_) async => stubFlow().approval);

    when(() => authService.session).thenReturn(
      AuthSession(
        accessToken: 'token',
        refreshToken: 'refresh',
        expiresAt: DateTime.now().add(const Duration(hours: 1)),
        user: const AuthenticatedUser(id: 99, name: '테스터', employeeNo: 'E099'),
        permissions: const <AuthPermission>[],
      ),
    );

    when(() => recallUseCase.call(any())).thenAnswer((_) async => stubFlow());
    when(() => resubmitUseCase.call(any())).thenAnswer((_) async => stubFlow());
  });

  tearDown(() async {
    await GetIt.I.reset();
    dotenv.clean();
  });

  testWidgets('플래그 Off 시 스펙 페이지를 노출한다', (tester) async {
    dotenv.testLoad(fileInput: 'FEATURE_APPROVALS_ENABLED=false\n');

    await tester.pumpWidget(_buildApp(const ApprovalHistoryPage()));
    await tester.pump();

    expect(find.text('결재 이력 조회'), findsWidgets);
    expect(find.text('결재 이력 기능 준비 중'), findsOneWidget);
  });

  testWidgets('이력 목록을 렌더링하고 검색 필터를 적용한다', (tester) async {
    dotenv.testLoad(fileInput: 'FEATURE_APPROVALS_ENABLED=true\n');

    when(
      () => historyRepository.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 => PaginatedResult<ApprovalHistoryRecord>(
        items: [record, secondRecord],
        page: 1,
        pageSize: 20,
        total: 2,
      ),
    );

    await tester.pumpWidget(_buildApp(const ApprovalHistoryPage()));
    await tester.pump();
    await tester.pumpAndSettle();

    expect(find.textContaining('APP-2024-0001'), findsOneWidget);
    expect(find.text('승인 완료'), findsOneWidget);

    await tester.enterText(find.byType(ShadInput).first, 'APP-2024');
    await tester.tap(find.text('검색 적용'));
    await tester.pump();

    verify(
      () => historyRepository.list(
        page: any(named: 'page'),
        pageSize: 20,
        query: 'APP-2024',
        approvalActionId: null,
        from: null,
        to: null,
      ),
    ).called(greaterThanOrEqualTo(1));
  });

  testWidgets('행위 필터 선택 시 approval_action_id로 재조회한다', (tester) async {
    dotenv.testLoad(fileInput: 'FEATURE_APPROVALS_ENABLED=true\n');

    final capturedActionIds = <int?>[];
    when(
      () => historyRepository.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((invocation) async {
      capturedActionIds.add(
        invocation.namedArguments[#approvalActionId] as int?,
      );
      return PaginatedResult<ApprovalHistoryRecord>(
        items: [record, secondRecord],
        page: 1,
        pageSize: 20,
        total: 2,
      );
    });

    await tester.pumpWidget(_buildApp(const ApprovalHistoryPage()));
    await tester.pumpAndSettle();

    final controller = tester
        .widgetList<AnimatedBuilder>(find.byType(AnimatedBuilder))
        .map((builder) => builder.animation)
        .whereType<ApprovalHistoryController>()
        .first;

    controller.updateActionFilter(ApprovalHistoryActionFilter.approve);
    await controller.fetch(page: 1);
    await tester.pump();

    expect(capturedActionIds, contains(11));
  });

  testWidgets('회수 시 상세 재조회 실패 안내를 노출한다', (tester) async {
    dotenv.testLoad(fileInput: 'FEATURE_APPROVALS_ENABLED=true\n');

    when(
      () => historyRepository.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 => PaginatedResult<ApprovalHistoryRecord>(
        items: [record],
        page: 1,
        pageSize: 20,
        total: 1,
      ),
    );

    final recallable = recallableFlow();
    var fetchCount = 0;
    when(
      () => approvalRepository.fetchDetail(
        any(),
        includeSteps: any(named: 'includeSteps'),
        includeHistories: any(named: 'includeHistories'),
      ),
    ).thenAnswer((_) async {
      if (fetchCount == 0) {
        fetchCount++;
        return recallable.approval;
      }
      fetchCount++;
      throw Exception('refresh failed');
    });

    await tester.pumpWidget(_buildApp(const ApprovalHistoryPage()));
    await tester.pump();
    await tester.pumpAndSettle();

    final table = tester.widget<SuperportTable>(find.byType(SuperportTable));
    table.onRowTap?.call(0);
    await tester.pump();
    await tester.pumpAndSettle();

    await tester.tap(find.widgetWithText(ShadButton, '회수').first);
    await tester.pump();
    await tester.pumpAndSettle();

    await tester.pumpAndSettle();

    expect(find.text('결재 회수'), findsOneWidget);
    final confirmButton = find.widgetWithText(ShadButton, '회수').last;
    await tester.ensureVisible(confirmButton);
    await tester.tap(confirmButton);
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 300));
    await tester.pump(const Duration(milliseconds: 500));

    expect(fetchCount, equals(2));
    expect(find.textContaining('결재 상세를 새로고침하지 못했습니다'), findsOneWidget);

    await tester.tap(find.byTooltip('닫기'));
    await tester.pumpAndSettle();
  });

  testWidgets('회수 성공 시 토스트를 노출한다', (tester) async {
    dotenv.testLoad(fileInput: 'FEATURE_APPROVALS_ENABLED=true\n');

    when(
      () => historyRepository.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 => PaginatedResult<ApprovalHistoryRecord>(
        items: [record],
        page: 1,
        pageSize: 20,
        total: 1,
      ),
    );

    final recallable = recallableFlow();
    when(
      () => approvalRepository.fetchDetail(
        any(),
        includeSteps: any(named: 'includeSteps'),
        includeHistories: any(named: 'includeHistories'),
      ),
    ).thenAnswer((_) async => recallable.approval);

    when(() => recallUseCase.call(any())).thenAnswer((_) async => recallable);

    await tester.pumpWidget(_buildApp(const ApprovalHistoryPage()));
    await tester.pumpAndSettle();

    final table = tester.widget<SuperportTable>(find.byType(SuperportTable));
    table.onRowTap?.call(0);
    await tester.pumpAndSettle();

    await tester.tap(find.widgetWithText(ShadButton, '회수').first);
    await tester.pumpAndSettle();

    expect(find.text('결재 회수'), findsOneWidget);
    await tester.tap(find.widgetWithText(ShadButton, '회수').last);
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 300));

    expect(find.text('결재(APP-2024-0001) 회수를 완료했습니다.'), findsOneWidget);
    verify(() => recallUseCase.call(any())).called(1);

    await tester.tap(find.byTooltip('닫기'));
    await tester.pumpAndSettle();
  });

  testWidgets('재상신 성공 시 토스트를 노출한다', (tester) async {
    dotenv.testLoad(fileInput: 'FEATURE_APPROVALS_ENABLED=true\n');

    when(
      () => historyRepository.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 => PaginatedResult<ApprovalHistoryRecord>(
        items: [record],
        page: 1,
        pageSize: 20,
        total: 1,
      ),
    );

    final flow = resubmittableFlow();
    when(
      () => approvalRepository.fetchDetail(
        any(),
        includeSteps: any(named: 'includeSteps'),
        includeHistories: any(named: 'includeHistories'),
      ),
    ).thenAnswer((_) async => flow.approval);

    when(() => resubmitUseCase.call(any())).thenAnswer((_) async => flow);

    await tester.pumpWidget(_buildApp(const ApprovalHistoryPage()));
    await tester.pumpAndSettle();

    final table = tester.widget<SuperportTable>(find.byType(SuperportTable));
    table.onRowTap?.call(0);
    await tester.pumpAndSettle();

    await tester.tap(find.widgetWithText(ShadButton, '재상신').first);
    await tester.pumpAndSettle();

    expect(find.text('결재 재상신'), findsOneWidget);
    await tester.tap(find.widgetWithText(ShadButton, '재상신').last);
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 300));

    expect(find.text('결재(APP-2024-0001) 재상신을 완료했습니다.'), findsOneWidget);
    verify(() => resubmitUseCase.call(any())).called(1);

    await tester.tap(find.byTooltip('닫기'));
    await tester.pumpAndSettle();
  });
}
