import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:intl/intl.dart' as intl;
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_proceed_status.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/dialogs/approval_history_detail_dialog.dart';
import 'package:superport_v2/features/auth/domain/entities/authenticated_user.dart';
import 'package:superport_v2/widgets/components/superport_dialog.dart';

import '../../../../../helpers/test_app.dart';

void main() {
  TestWidgetsFlutterBinding.ensureInitialized();

  final dateFormat = intl.DateFormat('yyyy-MM-dd HH:mm');

  late _FakeApprovalRepository approvalRepository;
  late _FakeApprovalHistoryRepository historyRepository;
  late ApprovalHistoryController controller;
  late ApprovalHistoryRecord record;
  late AuthenticatedUser user;
  late Approval sampleApproval;
  late ApprovalStatus statusInProgress;
  late ApprovalStatus statusCompleted;
  late ApprovalStatus statusRejected;

  setUp(() {
    approvalRepository = _FakeApprovalRepository();
    historyRepository = _FakeApprovalHistoryRepository();

    statusInProgress = ApprovalStatus(
      id: 1,
      name: '진행중',
      color: '#0EA5E9',
      isTerminal: false,
    );
    statusCompleted = ApprovalStatus(
      id: 2,
      name: '완료',
      color: '#22C55E',
      isTerminal: true,
    );
    statusRejected = ApprovalStatus(
      id: 3,
      name: '반려',
      color: '#F97316',
      isTerminal: true,
    );

    final requester = ApprovalRequester(
      id: 901,
      employeeNo: 'E901',
      name: '상신자',
    );
    final approver1 = ApprovalApprover(
      id: 801,
      employeeNo: 'E801',
      name: '선결자',
    );
    final approver2 = ApprovalApprover(
      id: 802,
      employeeNo: 'E802',
      name: '최종자',
    );

    final steps = [
      ApprovalStep(
        id: 1001,
        stepOrder: 1,
        approver: approver1,
        status: statusInProgress,
        assignedAt: DateTime(2024, 1, 10, 9),
        note: '1단계',
      ),
      ApprovalStep(
        id: 1002,
        stepOrder: 2,
        approver: approver2,
        status: statusCompleted,
        assignedAt: DateTime(2024, 1, 10, 10),
        decidedAt: DateTime(2024, 1, 10, 12),
        note: '2단계',
      ),
    ];

    final histories = [
      ApprovalHistory(
        id: 5001,
        action: ApprovalAction(id: 11, name: '상신', code: 'submit'),
        fromStatus: null,
        toStatus: statusInProgress,
        approver: approver1,
        actionAt: DateTime(2024, 1, 10, 9, 5),
        note: '상신 완료',
      ),
      ApprovalHistory(
        id: 5002,
        action: ApprovalAction(id: 12, name: '승인', code: 'approve'),
        fromStatus: statusInProgress,
        toStatus: statusCompleted,
        approver: approver2,
        actionAt: DateTime(2024, 1, 10, 12, 30),
        note: '승인 완료',
      ),
    ];

    sampleApproval = Approval(
      id: 300,
      approvalNo: 'APP-2024-0300',
      transactionNo: 'TRX-0300',
      transactionUpdatedAt: DateTime(2024, 1, 10, 12, 45),
      status: statusInProgress,
      requester: requester,
      requestedAt: DateTime(2024, 1, 10, 8, 30),
      steps: steps,
      histories: histories,
      updatedAt: DateTime(2024, 1, 10, 12, 50),
    );

    approvalRepository
      ..detail = sampleApproval
      ..recallResult = sampleApproval
      ..resubmitResult = sampleApproval.copyWith(
        status: statusInProgress,
        updatedAt: DateTime(2024, 1, 10, 13),
      )
      ..historyResult = PaginatedResult<ApprovalHistory>(
        items: histories,
        page: 1,
        pageSize: 20,
        total: histories.length,
      );

    historyRepository.listResult = PaginatedResult<ApprovalHistoryRecord>(
      items: [
        ApprovalHistoryRecord(
          id: 700,
          approvalId: sampleApproval.id!,
          approvalNo: sampleApproval.approvalNo,
          stepOrder: 1,
          action: ApprovalAction(id: 21, name: '상신 완료', code: 'submit'),
          fromStatus: null,
          toStatus: statusInProgress,
          approver: approver1,
          actionAt: DateTime(2024, 1, 10, 9, 5),
          note: '상신 후 대기 중',
        ),
      ],
      page: 1,
      pageSize: 20,
      total: 1,
    );

    controller = ApprovalHistoryController(
      repository: historyRepository,
      approvalRepository: approvalRepository,
      recallUseCase: RecallApprovalUseCase(repository: approvalRepository),
      resubmitUseCase: ResubmitApprovalUseCase(repository: approvalRepository),
    );

    record = historyRepository.listResult!.items.first;

    user = const AuthenticatedUser(id: 42, name: '결재자', employeeNo: 'E042');
  });

  tearDown(() {
    controller.dispose();
  });

  Future<void> openDialog(WidgetTester tester) async {
    await tester.pumpWidget(buildTestApp(const SizedBox.shrink()));
    final context = tester.element(find.byType(SizedBox));

    unawaited(
      showApprovalHistoryDetailDialog(
        context: context,
        controller: controller,
        record: record,
        dateFormat: dateFormat,
        currentUser: user,
      ),
    );

    await tester.pump();
    await tester.pumpAndSettle();
  }

  testWidgets('showApprovalHistoryDetailDialog 결재 요약과 타임라인을 표시한다', (
    tester,
  ) async {
    await openDialog(tester);

    expect(find.text('결재 이력 상세'), findsOneWidget);
    expect(find.textContaining('결재번호 ${record.approvalNo}'), findsWidgets);
    expect(find.text('상태 타임라인'), findsOneWidget);
    expect(find.text('감사 로그'), findsOneWidget);

    expect(
      find.textContaining(
        '상신자 ${sampleApproval.requester.name} (${sampleApproval.requester.employeeNo})',
      ),
      findsOneWidget,
    );
    expect(
      find.textContaining('총 ${sampleApproval.steps.length}단계'),
      findsOneWidget,
    );
    expect(find.text('승인'), findsWidgets);

    expect(approvalRepository.listHistoryCalls, isNotEmpty);
  });

  testWidgets('회수 버튼을 누르면 recallApproval이 호출되어 감사 로그가 새로고침된다', (tester) async {
    await openDialog(tester);

    final recallButton = find.widgetWithText(ShadButton, '회수');
    expect(recallButton, findsOneWidget);
    await tester.ensureVisible(recallButton);
    await tester.tap(recallButton, warnIfMissed: false);
    await tester.pumpAndSettle();

    final dialogFinder = find.ancestor(
      of: find.text('결재 회수'),
      matching: find.byType(SuperportDialog),
    );
    expect(dialogFinder, findsOneWidget);

    final memoField = find.descendant(
      of: dialogFinder,
      matching: find.byType(ShadTextarea),
    );
    expect(memoField, findsOneWidget);
    await tester.enterText(memoField, '긴급 회수');

    final confirmButton = find.descendant(
      of: dialogFinder,
      matching: find.widgetWithText(ShadButton, '회수'),
    );
    await tester.tap(confirmButton, warnIfMissed: false);
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));
    await tester.pumpAndSettle();

    expect(approvalRepository.recallInputs, hasLength(1));
    expect(approvalRepository.recallInputs.first.note, '긴급 회수');
    expect(approvalRepository.listHistoryCalls.length, greaterThanOrEqualTo(2));
  });

  testWidgets('재상신 버튼을 누르면 resubmitApproval이 호출되어 최신 단계 정보가 전달된다', (
    tester,
  ) async {
    final rejectedApproval = sampleApproval.copyWith(
      status: statusRejected,
      steps: sampleApproval.steps
          .map(
            (step) => step.stepOrder == 1
                ? step.copyWith(
                    status: statusRejected,
                    decidedAt: DateTime(2024, 1, 10, 11),
                  )
                : step,
          )
          .toList(growable: false),
    );
    final resubmittedStatus = ApprovalStatus(
      id: 4,
      name: '재상신',
      color: '#6366F1',
      isTerminal: false,
    );
    final resubmittedApproval = rejectedApproval.copyWith(
      status: resubmittedStatus,
      updatedAt: DateTime(2024, 1, 10, 13, 10),
    );

    approvalRepository
      ..detail = rejectedApproval
      ..resubmitResult = resubmittedApproval;
    record = record.copyWith(
      action: ApprovalAction(id: 33, name: '반려', code: 'reject'),
      toStatus: statusRejected,
      stepOrder: 2,
    );

    await openDialog(tester);

    final resubmitButton = find.widgetWithText(ShadButton, '재상신');
    expect(resubmitButton, findsOneWidget);
    await tester.ensureVisible(resubmitButton);
    await tester.tap(resubmitButton, warnIfMissed: false);
    await tester.pumpAndSettle();

    final dialogFinder = find.ancestor(
      of: find.text('결재 재상신'),
      matching: find.byType(SuperportDialog),
    );
    expect(dialogFinder, findsOneWidget);

    final memoField = find.descendant(
      of: dialogFinder,
      matching: find.byType(ShadTextarea),
    );
    expect(memoField, findsOneWidget);
    await tester.enterText(memoField, '재상신 메모');

    final confirmButton = find.descendant(
      of: dialogFinder,
      matching: find.widgetWithText(ShadButton, '재상신'),
    );
    await tester.tap(confirmButton, warnIfMissed: false);
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));
    await tester.pumpAndSettle();

    expect(approvalRepository.resubmitInputs, hasLength(1));
    final input = approvalRepository.resubmitInputs.first;
    expect(input.note, '재상신 메모');
    expect(input.submission.steps.length, rejectedApproval.steps.length);
    expect(
      input.submission.steps.first.stepOrder,
      rejectedApproval.steps.first.stepOrder,
    );
    expect(approvalRepository.listHistoryCalls.length, greaterThanOrEqualTo(2));
  });
}

class _FakeApprovalRepository implements ApprovalRepository {
  Approval? detail;
  Approval? recallResult;
  Approval? resubmitResult;
  PaginatedResult<ApprovalHistory>? historyResult;

  final List<ApprovalRecallInput> recallInputs = [];
  final List<ApprovalResubmissionInput> resubmitInputs = [];
  final List<int> fetchDetailIds = [];
  final List<_ListHistoryCall> listHistoryCalls = [];

  @override
  Future<Approval> assignSteps(ApprovalStepAssignmentInput input) {
    throw UnimplementedError();
  }

  @override
  Future<Approval> fetchDetail(
    int id, {
    bool includeSteps = true,
    bool includeHistories = true,
  }) async {
    fetchDetailIds.add(id);
    final result = detail;
    if (result == null) {
      throw StateError('detail이 설정되지 않았습니다.');
    }
    return result;
  }

  @override
  Future<ApprovalProceedStatus> canProceed(int id) {
    throw UnimplementedError();
  }

  @override
  Future<List<ApprovalAction>> listActions({bool activeOnly = true}) {
    throw UnimplementedError();
  }

  @override
  Future<Approval> performStepAction(ApprovalStepActionInput input) {
    throw UnimplementedError();
  }

  @override
  Future<PaginatedResult<Approval>> list({
    int page = 1,
    int pageSize = 20,
    int? transactionId,
    int? approvalStatusId,
    int? requestedById,
    List<String>? statusCodes,
    bool includePending = false,
    bool includeHistories = false,
    bool includeSteps = false,
  }) {
    throw UnimplementedError();
  }

  @override
  Future<Approval> submit(ApprovalSubmissionInput input) {
    throw UnimplementedError();
  }

  @override
  Future<Approval> resubmit(ApprovalResubmissionInput input) async {
    resubmitInputs.add(input);
    final result = resubmitResult ?? detail;
    if (result == null) {
      throw StateError('resubmitResult가 설정되지 않았습니다.');
    }
    return result;
  }

  @override
  Future<Approval> approve(ApprovalDecisionInput input) {
    throw UnimplementedError();
  }

  @override
  Future<Approval> reject(ApprovalDecisionInput input) {
    throw UnimplementedError();
  }

  @override
  Future<Approval> recall(ApprovalRecallInput input) async {
    recallInputs.add(input);
    final result = recallResult ?? detail;
    if (result == null) {
      throw StateError('recallResult가 설정되지 않았습니다.');
    }
    return result;
  }

  @override
  Future<PaginatedResult<ApprovalHistory>> listHistory({
    required int approvalId,
    int page = 1,
    int pageSize = 20,
    DateTime? from,
    DateTime? to,
    int? actorId,
    int? approvalActionId,
  }) async {
    listHistoryCalls.add(
      _ListHistoryCall(
        approvalId: approvalId,
        page: page,
        pageSize: pageSize,
        actorId: actorId,
        approvalActionId: approvalActionId,
      ),
    );
    return historyResult ??
        PaginatedResult<ApprovalHistory>(
          items: const [],
          page: page,
          pageSize: pageSize,
          total: 0,
        );
  }

  @override
  Future<Approval> create(ApprovalCreateInput input) {
    throw UnimplementedError();
  }

  @override
  Future<Approval> update(ApprovalUpdateInput input) {
    throw UnimplementedError();
  }

  @override
  Future<void> delete(int id) {
    throw UnimplementedError();
  }

  @override
  Future<Approval> restore(int id) {
    throw UnimplementedError();
  }
}

class _FakeApprovalHistoryRepository implements ApprovalHistoryRepository {
  PaginatedResult<ApprovalHistoryRecord>? listResult;
  final List<_HistoryListCall> listCalls = [];

  @override
  Future<PaginatedResult<ApprovalHistoryRecord>> list({
    int page = 1,
    int pageSize = 20,
    String? query,
    int? approvalActionId,
    DateTime? from,
    DateTime? to,
  }) async {
    listCalls.add(
      _HistoryListCall(
        page: page,
        pageSize: pageSize,
        query: query,
        approvalActionId: approvalActionId,
        from: from,
        to: to,
      ),
    );
    return listResult ??
        PaginatedResult<ApprovalHistoryRecord>(
          items: const [],
          page: page,
          pageSize: pageSize,
          total: 0,
        );
  }
}

class _ListHistoryCall {
  _ListHistoryCall({
    required this.approvalId,
    required this.page,
    required this.pageSize,
    this.actorId,
    this.approvalActionId,
  });

  final int approvalId;
  final int page;
  final int pageSize;
  final int? actorId;
  final int? approvalActionId;
}

class _HistoryListCall {
  _HistoryListCall({
    required this.page,
    required this.pageSize,
    this.query,
    this.approvalActionId,
    this.from,
    this.to,
  });

  final int page;
  final int pageSize;
  final String? query;
  final int? approvalActionId;
  final DateTime? from;
  final DateTime? to;
}
