import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:shadcn_ui/shadcn_ui.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/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/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() {
  TestWidgetsFlutterBinding.ensureInitialized();

  late ApprovalHistoryRepository historyRepository;
  late ApprovalRepository approvalRepository;
  late RecallApprovalUseCase recallUseCase;
  late ResubmitApprovalUseCase resubmitUseCase;

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

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

  ApprovalFlow buildRecallableFlow() {
    final status = ApprovalStatus(id: 1, name: '진행중');
    final steps = [
      ApprovalStep(
        id: 1,
        stepOrder: 1,
        approver: ApprovalApprover(id: 101, employeeNo: 'E101', name: '1차'),
        status: status,
        assignedAt: DateTime(2024, 4, 1, 9),
      ),
      ApprovalStep(
        id: 2,
        stepOrder: 2,
        approver: ApprovalApprover(id: 102, employeeNo: 'E102', name: '2차'),
        status: status,
        assignedAt: DateTime(2024, 4, 1, 10),
        decidedAt: DateTime(2024, 4, 1, 10, 30),
      ),
    ];
    final approval = Approval(
      id: 500,
      approvalNo: 'APP-500',
      transactionId: 5000,
      transactionNo: 'TRX-500',
      status: status,
      requester: ApprovalRequester(id: 90, employeeNo: 'E090', name: '상신자'),
      requestedAt: DateTime(2024, 4, 1),
      steps: steps,
      histories: const [],
      updatedAt: DateTime(2024, 4, 1, 12),
      currentStep: steps.first,
      transactionUpdatedAt: DateTime(2024, 4, 1, 11, 30),
    );
    return ApprovalFlow(approval: approval);
  }

  ApprovalFlow buildResubmittableFlow() {
    final rejectedStatus = ApprovalStatus(id: 9, name: '반려', isTerminal: true);
    final steps = [
      ApprovalStep(
        id: 1,
        stepOrder: 1,
        approver: ApprovalApprover(id: 101, employeeNo: 'E101', name: '1차'),
        status: rejectedStatus,
        assignedAt: DateTime(2024, 3, 30, 9),
        decidedAt: DateTime(2024, 3, 30, 9, 30),
      ),
      ApprovalStep(
        id: 2,
        stepOrder: 2,
        approver: ApprovalApprover(id: 102, employeeNo: 'E102', name: '최종'),
        status: rejectedStatus,
        assignedAt: DateTime(2024, 3, 30, 10),
        decidedAt: DateTime(2024, 3, 30, 10, 30),
        note: '보완 필요',
      ),
    ];
    final approval = Approval(
      id: 600,
      approvalNo: 'APP-600',
      transactionId: 6000,
      transactionNo: 'TRX-600',
      status: rejectedStatus,
      requester: ApprovalRequester(id: 90, employeeNo: 'E090', name: '상신자'),
      requestedAt: DateTime(2024, 3, 30),
      decidedAt: DateTime(2024, 3, 30, 12),
      note: '반려 메모',
      steps: steps,
      histories: const [],
      updatedAt: DateTime(2024, 3, 30, 12),
      currentStep: null,
      transactionUpdatedAt: DateTime(2024, 3, 30, 11, 45),
    );
    return ApprovalFlow(approval: approval);
  }

  Future<ApprovalHistoryController> buildController(ApprovalFlow flow) async {
    final controller = ApprovalHistoryController(
      repository: historyRepository,
      approvalRepository: approvalRepository,
      recallUseCase: recallUseCase,
      resubmitUseCase: resubmitUseCase,
    );

    when(
      () => approvalRepository.fetchDetail(
        flow.id!,
        includeSteps: any(named: 'includeSteps'),
        includeHistories: any(named: 'includeHistories'),
      ),
    ).thenAnswer((_) async => flow.approval);
    when(() => approvalRepository.canProceed(flow.id!)).thenAnswer(
      (_) async => ApprovalProceedStatus(
        approvalId: flow.id!,
        canProceed: flow.currentStep != null,
      ),
    );

    await controller.loadApprovalFlow(flow.id!);
    return controller;
  }

  testWidgets('회수 버튼이 노트를 전달해 유즈케이스를 호출한다', (tester) async {
    final flow = buildRecallableFlow();
    final controller = await buildController(flow);

    ApprovalRecallInput? captured;
    when(() => recallUseCase.call(any())).thenAnswer((invocation) async {
      captured = invocation.positionalArguments.first as ApprovalRecallInput;
      return flow;
    });

    await tester.pumpWidget(
      _ApprovalActionHarness(controller: controller, actorId: 99),
    );
    await tester.pumpAndSettle();

    await tester.enterText(
      find.byKey(const ValueKey('recall-note-field')),
      '  긴급 회수  ',
    );
    await tester.tap(find.text('회수'));
    await tester.pumpAndSettle();

    expect(captured, isNotNull);
    expect(captured?.approvalId, flow.id);
    expect(captured?.actorId, 99);
    expect(captured?.note, '긴급 회수');
    expect(captured?.transactionExpectedUpdatedAt, flow.transactionUpdatedAt);
  });

  testWidgets('재상신 버튼이 템플릿 단계를 포함해 유즈케이스를 호출한다', (tester) async {
    final flow = buildResubmittableFlow();
    final controller = await buildController(flow);

    ApprovalResubmissionInput? captured;
    when(() => resubmitUseCase.call(any())).thenAnswer((invocation) async {
      captured =
          invocation.positionalArguments.first as ApprovalResubmissionInput;
      return flow;
    });

    await tester.pumpWidget(
      _ApprovalActionHarness(controller: controller, actorId: 99),
    );
    await tester.pumpAndSettle();

    await tester.enterText(
      find.byKey(const ValueKey('resubmit-note-field')),
      ' 재상신 메모 ',
    );
    await tester.tap(find.text('재상신'));
    await tester.pumpAndSettle();

    expect(captured, isNotNull);
    expect(captured?.actorId, 99);
    expect(captured?.note, '재상신 메모');
    expect(captured?.submission.steps.length, flow.steps.length);
    expect(captured?.submission.requesterId, flow.requester.id);
    expect(captured?.submission.transactionId, flow.transactionId);
    expect(captured?.transactionExpectedUpdatedAt, flow.transactionUpdatedAt);
  });
}

class _ApprovalActionHarness extends StatefulWidget {
  const _ApprovalActionHarness({
    required this.controller,
    required this.actorId,
  });

  final ApprovalHistoryController controller;
  final int actorId;

  @override
  State<_ApprovalActionHarness> createState() => _ApprovalActionHarnessState();
}

class _ApprovalActionHarnessState extends State<_ApprovalActionHarness> {
  final TextEditingController _recallNoteController = TextEditingController();
  final TextEditingController _resubmitNoteController = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: ShadTheme(
        data: ShadThemeData(
          colorScheme: const ShadSlateColorScheme.light(),
          brightness: Brightness.light,
        ),
        child: Scaffold(
          body: AnimatedBuilder(
            animation: widget.controller,
            builder: (context, _) {
              final flow = widget.controller.selectedFlow;
              if (flow == null) {
                return const SizedBox.shrink();
              }
              final canRecall = _canRecall(flow);
              final canResubmit = _canResubmit(flow);
              return Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    ShadInput(
                      key: const ValueKey('recall-note-field'),
                      controller: _recallNoteController,
                      placeholder: const Text('회수 사유'),
                    ),
                    const SizedBox(height: 12),
                    ShadButton(
                      onPressed: canRecall ? () => _handleRecall(flow) : null,
                      child: const Text('회수'),
                    ),
                    const SizedBox(height: 24),
                    ShadInput(
                      key: const ValueKey('resubmit-note-field'),
                      controller: _resubmitNoteController,
                      placeholder: const Text('재상신 메모'),
                    ),
                    const SizedBox(height: 12),
                    ShadButton.outline(
                      onPressed: canResubmit
                          ? () => _handleResubmit(flow)
                          : null,
                      child: const Text('재상신'),
                    ),
                  ],
                ),
              );
            },
          ),
        ),
      ),
    );
  }

  bool _canRecall(ApprovalFlow flow) {
    if (flow.status.isTerminal) {
      return false;
    }
    if (flow.steps.isEmpty) {
      return false;
    }
    return flow.steps.first.decidedAt == null;
  }

  bool _canResubmit(ApprovalFlow flow) {
    if (!flow.status.isTerminal) {
      return false;
    }
    final statusName = flow.status.name.toLowerCase();
    return statusName.contains('반려') || statusName.contains('reject');
  }

  Future<void> _handleRecall(ApprovalFlow flow) async {
    final latestFlow = await widget.controller.refreshFlow(flow.id!) ?? flow;
    final transactionUpdatedAt = latestFlow.transactionUpdatedAt;
    if (transactionUpdatedAt == null) {
      return;
    }
    final input = ApprovalRecallInput(
      approvalId: latestFlow.id!,
      actorId: widget.actorId,
      note: _trimmedOrNull(_recallNoteController.text),
      expectedUpdatedAt: latestFlow.approval.updatedAt,
      transactionExpectedUpdatedAt: transactionUpdatedAt,
    );
    await widget.controller.recallApproval(input);
  }

  Future<void> _handleResubmit(ApprovalFlow flow) async {
    final latestFlow = await widget.controller.refreshFlow(flow.id!) ?? flow;
    final transactionUpdatedAt = latestFlow.transactionUpdatedAt;
    if (transactionUpdatedAt == null) {
      return;
    }
    final steps = latestFlow.steps
        .map(
          (step) => ApprovalStepAssignmentItem(
            stepOrder: step.stepOrder,
            approverId: step.approver.id,
            note: step.note,
          ),
        )
        .toList(growable: false);
    final submission = ApprovalSubmissionInput(
      transactionId: latestFlow.transactionId,
      statusId: latestFlow.status.id,
      requesterId: latestFlow.requester.id,
      finalApproverId: latestFlow.finalApprover?.id,
      note: latestFlow.note,
      steps: steps,
    );
    final input = ApprovalResubmissionInput(
      approvalId: latestFlow.id!,
      actorId: widget.actorId,
      submission: submission,
      note: _trimmedOrNull(_resubmitNoteController.text),
      expectedUpdatedAt: latestFlow.approval.updatedAt,
      transactionExpectedUpdatedAt: transactionUpdatedAt,
    );
    await widget.controller.resubmitApproval(input);
  }

  String? _trimmedOrNull(String value) {
    final trimmed = value.trim();
    if (trimmed.isEmpty) {
      return null;
    }
    return trimmed;
  }

  @override
  void dispose() {
    _recallNoteController.dispose();
    _resubmitNoteController.dispose();
    super.dispose();
  }
}
