import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.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/entities/approval_template.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/approve_approval_use_case.dart';
import 'package:superport_v2/features/approvals/presentation/controllers/approval_controller.dart';
import 'package:superport_v2/features/inventory/transactions/domain/entities/stock_transaction.dart';
import 'package:superport_v2/features/inventory/transactions/domain/entities/stock_transaction_input.dart';
import 'package:superport_v2/features/inventory/transactions/domain/repositories/stock_transaction_repository.dart';

void main() {
  const runFlow = bool.fromEnvironment('STAGING_RUN_APPROVAL_FLOW');
  const useFakeFlow = bool.fromEnvironment('STAGING_USE_FAKE_APPROVAL_FLOW');

  if (!runFlow) {
    testWidgets(
      'approval flow e2e (환경 변수 설정 필요: STAGING_RUN_APPROVAL_FLOW=true)',
      (tester) async {
        tester.printToConsole(
          '통합 테스트를 실행하려면 STAGING_RUN_APPROVAL_FLOW=true 를 설정하세요.',
        );
      },
      skip: true,
    );
    return;
  }

  IntegrationTestWidgetsFlutterBinding.ensureInitialized();

  final missingConfigs = <String>[];
  if (!useFakeFlow) {
    missingConfigs.add('STAGING_USE_FAKE_APPROVAL_FLOW=true');
  }

  if (missingConfigs.isNotEmpty) {
    testWidgets(
      'approval flow e2e (환경 변수 설정 필요: ${missingConfigs.join(', ')})',
      (tester) async {
        tester.printToConsole(
          '결재 통합 테스트를 실행하려면 다음 변수를 설정하세요: ${missingConfigs.join(', ')}',
        );
      },
      skip: true,
    );
    return;
  }

  testWidgets('inventory inbound → approval submit → approver hand-off', (
    tester,
  ) async {
    const transactionTypeId = 501;
    const transactionStatusId = 10;
    const warehouseId = 30;
    const requesterId = 91;
    const firstApproverId = 201;
    const secondApproverId = 202;

    final stockRepository = _FakeStockTransactionRepository(
      transactionTypeId: transactionTypeId,
      initialStatusId: transactionStatusId,
      warehouseId: warehouseId,
      employeeId: requesterId,
    );

    final approvalRepository = _FakeApprovalRepository();
    final templateRepository = _FakeApprovalTemplateRepository();
    final controller = ApprovalController(
      approvalRepository: approvalRepository,
      templateRepository: templateRepository,
    );
    final approveUseCase = ApproveApprovalUseCase(
      repository: approvalRepository,
    );

    final now = DateTime.now();
    final transactionInput = StockTransactionCreateInput(
      transactionTypeId: transactionTypeId,
      transactionStatusId: transactionStatusId,
      warehouseId: warehouseId,
      transactionDate: now,
      createdById: requesterId,
      note: 'integration-test ${now.toIso8601String()}',
      lines: [
        TransactionLineCreateInput(
          lineNo: 1,
          productId: 7001,
          quantity: 5,
          unitPrice: 1200,
        ),
      ],
      customers: [TransactionCustomerCreateInput(customerId: 4001)],
      approval: StockTransactionApprovalInput(
        requestedById: requesterId,
        steps: [
          ApprovalStepAssignmentItem(stepOrder: 1, approverId: firstApproverId),
          ApprovalStepAssignmentItem(
            stepOrder: 2,
            approverId: secondApproverId,
          ),
        ],
        note: '입고 결재 테스트',
      ),
    );

    final createdTransaction = await stockRepository.create(transactionInput);
    expect(createdTransaction.id, isNotNull);
    tester.printToConsole('created transaction: ${createdTransaction.id}');

    final approvalSubmission = ApprovalSubmissionInput(
      transactionId: createdTransaction.id,
      statusId: 1,
      requesterId: requesterId,
      note: transactionInput.approval.note,
      steps: transactionInput.approval.steps,
    );

    final submittedApproval = await approvalRepository.submit(
      approvalSubmission,
    );
    expect(submittedApproval.id, isNotNull);
    tester.printToConsole('submitted approval: ${submittedApproval.id}');

    await controller.fetch();
    final approvals = controller.result?.items ?? const [];
    expect(approvals, isNotEmpty);
    expect(approvals.first.id, submittedApproval.id);

    await controller.selectApproval(submittedApproval.id!);
    expect(controller.selected?.currentStep?.stepOrder, 1);

    final firstFlow = await approveUseCase(
      ApprovalDecisionInput(
        approvalId: submittedApproval.id!,
        actorId: firstApproverId,
      ),
    );
    expect(firstFlow.currentStep?.stepOrder, 2);
    tester.printToConsole('first approval completed, moved to step 2');

    await controller.selectApproval(firstFlow.id!);
    expect(controller.selected?.currentStep?.stepOrder, 2);
    expect(controller.canProceedSelected, isTrue);

    final finalFlow = await approveUseCase(
      ApprovalDecisionInput(
        approvalId: firstFlow.id!,
        actorId: secondApproverId,
      ),
    );
    expect(finalFlow.currentStep, isNull);
    expect(finalFlow.status.isTerminal, isTrue);
    tester.printToConsole('approval completed by final approver');

    await controller.selectApproval(finalFlow.id!);
    expect(controller.selected?.status.isTerminal, isTrue);
    expect(controller.canProceedSelected, isFalse);
  });
}

class _FakeStockTransactionRepository implements StockTransactionRepository {
  _FakeStockTransactionRepository({
    required this.transactionTypeId,
    required this.initialStatusId,
    required this.warehouseId,
    required this.employeeId,
  });

  final int transactionTypeId;
  final int initialStatusId;
  final int warehouseId;
  final int employeeId;

  int _sequence = 1;
  final Map<int, StockTransaction> _transactions = {};

  @override
  Future<StockTransaction> create(StockTransactionCreateInput input) async {
    final id = _sequence++;
    final transaction = StockTransaction(
      id: id,
      transactionNo: 'TRX-${id.toString().padLeft(6, '0')}',
      transactionDate: input.transactionDate,
      type: StockTransactionType(id: transactionTypeId, name: '입고'),
      status: StockTransactionStatus(id: initialStatusId, name: '작성중'),
      warehouse: StockTransactionWarehouse(
        id: warehouseId,
        code: 'WH-$warehouseId',
        name: '테스트 창고',
      ),
      createdBy: StockTransactionEmployee(
        id: employeeId,
        employeeNo: 'EMP-$employeeId',
        name: '작성자',
      ),
      note: input.note,
      createdAt: DateTime.now(),
      updatedAt: DateTime.now(),
      lines: input.lines
          .map(
            (line) => StockTransactionLine(
              id: line.lineNo,
              lineNo: line.lineNo,
              product: StockTransactionProduct(
                id: line.productId,
                code: 'PRD-${line.productId}',
                name: '테스트 품목',
              ),
              quantity: line.quantity,
              unitPrice: line.unitPrice,
              note: line.note,
            ),
          )
          .toList(growable: false),
      customers: input.customers
          .map(
            (customer) => StockTransactionCustomer(
              id: customer.customerId,
              customer: StockTransactionCustomerSummary(
                id: customer.customerId,
                code: 'CUST-${customer.customerId}',
                name: '거래처',
              ),
              note: customer.note,
            ),
          )
          .toList(growable: false),
    );
    _transactions[id] = transaction;
    return transaction;
  }

  @override
  Future<StockTransaction> submit(int id, {String? note}) async {
    final transaction = _require(id);
    final updated = transaction.copyWith(
      status: StockTransactionStatus(id: transaction.status.id, name: '제출'),
      updatedAt: DateTime.now(),
    );
    _transactions[id] = updated;
    return updated;
  }

  StockTransaction _require(int id) {
    final transaction = _transactions[id];
    if (transaction == null) {
      throw StateError('트랜잭션($id)을 찾을 수 없습니다.');
    }
    return transaction;
  }

  @override
  Future<PaginatedResult<StockTransaction>> list({
    StockTransactionListFilter? filter,
  }) async {
    return PaginatedResult(
      items: _transactions.values.toList(growable: false),
      page: 1,
      pageSize: _transactions.length,
      total: _transactions.length,
    );
  }

  @override
  Future<StockTransaction> fetchDetail(int id, {List<String>? include}) async {
    return _require(id);
  }

  @override
  Future<StockTransaction> update(int id, StockTransactionUpdateInput input) =>
      throw UnimplementedError();

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

  @override
  Future<StockTransaction> restore(int id) => throw UnimplementedError();

  @override
  Future<StockTransaction> complete(int id, {String? note}) =>
      throw UnimplementedError();

  @override
  Future<StockTransaction> approve(int id, {String? note}) =>
      throw UnimplementedError();

  @override
  Future<StockTransaction> reject(int id, {String? note}) =>
      throw UnimplementedError();

  @override
  Future<StockTransaction> cancel(int id, {String? note}) =>
      throw UnimplementedError();
}

class _FakeApprovalRepository implements ApprovalRepository {
  int _sequence = 1000;
  final Map<int, Approval> _approvals = {};

  @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,
  }) async {
    final items = _approvals.values.toList(growable: false);
    return PaginatedResult(
      items: items,
      page: 1,
      pageSize: items.length,
      total: items.length,
    );
  }

  @override
  Future<Approval> fetchDetail(
    int id, {
    bool includeSteps = true,
    bool includeHistories = true,
  }) async {
    final approval = _approvals[id];
    if (approval == null) {
      throw StateError('결재($id)를 찾을 수 없습니다.');
    }
    return approval;
  }

  @override
  Future<Approval> submit(ApprovalSubmissionInput input) async {
    final id = _sequence++;
    final approvalNo = 'APP-${id.toString().padLeft(6, '0')}';
    final status = ApprovalStatus(id: input.statusId, name: '진행중');
    final requester = ApprovalRequester(
      id: input.requesterId,
      employeeNo: 'EMP-${input.requesterId}',
      name: '상신자',
    );
    final steps = input.steps
        .map(
          (step) => ApprovalStep(
            id: step.stepOrder,
            stepOrder: step.stepOrder,
            approver: ApprovalApprover(
              id: step.approverId,
              employeeNo: 'EMP-${step.approverId}',
              name: '승인자 ${step.approverId}',
            ),
            status: ApprovalStatus(id: 1, name: '대기'),
            assignedAt: DateTime.now(),
            note: step.note,
          ),
        )
        .toList(growable: false);
    final approval = Approval(
      id: id,
      approvalNo: approvalNo,
      transactionNo: input.transactionId != null
          ? 'TRX-${input.transactionId}'
          : 'TRX',
      status: status,
      requester: requester,
      requestedAt: DateTime.now(),
      note: input.note,
      steps: steps,
      histories: const [],
      currentStep: steps.isEmpty ? null : steps.first,
    );
    _approvals[id] = approval;
    return approval;
  }

  @override
  Future<Approval> approve(ApprovalDecisionInput input) async {
    final approval = await fetchDetail(input.approvalId);
    final current = approval.steps.firstWhere(
      (step) => step.decidedAt == null,
      orElse: () => throw StateError('모든 결재 단계가 이미 완료되었습니다.'),
    );
    if (current.approver.id != input.actorId) {
      throw StateError('현재 단계 승인자가 아닙니다.');
    }
    final steps = approval.steps
        .map((step) {
          if (step.decidedAt != null) {
            return step;
          }
          if (step.approver.id != input.actorId) {
            return step;
          }
          return step.copyWith(
            decidedAt: DateTime.now(),
            status: ApprovalStatus(id: 2, name: '승인됨', isTerminal: false),
          );
        })
        .toList(growable: false);

    final nextPending = steps.firstWhere(
      (step) => step.decidedAt == null,
      orElse: () => ApprovalStep(
        id: -1,
        stepOrder: -1,
        approver: ApprovalApprover(
          id: approval.requester.id,
          employeeNo: approval.requester.employeeNo,
          name: approval.requester.name,
        ),
        status: ApprovalStatus(id: 2, name: '완료', isTerminal: true),
        assignedAt: DateTime.now(),
      ),
    );

    ApprovalStatus nextStatus;
    ApprovalStep? currentStep;
    if (nextPending.stepOrder == -1) {
      nextStatus = ApprovalStatus(id: 9, name: '승인 완료', isTerminal: true);
      currentStep = null;
    } else {
      nextStatus = approval.status;
      currentStep = steps.firstWhere((step) => step.decidedAt == null);
    }

    final updated = approval.copyWith(
      status: nextStatus,
      steps: steps,
      currentStep: currentStep,
      updatedAt: DateTime.now(),
    );
    _approvals[input.approvalId] = updated;
    return updated;
  }

  @override
  Future<ApprovalProceedStatus> canProceed(int id) async {
    final approval = await fetchDetail(id);
    final nextPending = approval.steps.firstWhere(
      (step) => step.decidedAt == null,
      orElse: () => ApprovalStep(
        id: -1,
        stepOrder: -1,
        approver: ApprovalApprover(
          id: approval.requester.id,
          employeeNo: approval.requester.employeeNo,
          name: approval.requester.name,
        ),
        status: approval.status,
        assignedAt: DateTime.now(),
      ),
    );
    if (nextPending.stepOrder == -1) {
      return ApprovalProceedStatus(
        approvalId: id,
        canProceed: false,
        reason: '모든 단계가 완료되었습니다.',
      );
    }
    return ApprovalProceedStatus(
      approvalId: id,
      canProceed: true,
      reason: null,
    );
  }

  @override
  Future<List<ApprovalAction>> listActions({bool activeOnly = true}) async {
    return [
      ApprovalAction(id: 1, name: '승인', code: 'approve'),
      ApprovalAction(id: 2, name: '반려', code: 'reject'),
    ];
  }

  @override
  Future<Approval> resubmit(ApprovalResubmissionInput input) =>
      throw UnimplementedError();

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

  @override
  Future<Approval> recall(ApprovalRecallInput input) =>
      throw UnimplementedError();

  @override
  Future<PaginatedResult<ApprovalHistory>> listHistory({
    required int approvalId,
    int page = 1,
    int pageSize = 20,
    DateTime? from,
    DateTime? to,
    int? actorId,
    int? approvalActionId,
  }) => throw UnimplementedError();

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

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

  @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 _FakeApprovalTemplateRepository implements ApprovalTemplateRepository {
  @override
  Future<PaginatedResult<ApprovalTemplate>> list({
    int page = 1,
    int pageSize = 20,
    String? query,
    bool? isActive,
  }) async {
    return PaginatedResult(
      items: const <ApprovalTemplate>[],
      page: 1,
      pageSize: 0,
      total: 0,
    );
  }

  @override
  Future<ApprovalTemplate> fetchDetail(int id, {bool includeSteps = true}) =>
      throw UnimplementedError();

  @override
  Future<ApprovalTemplate> create(
    ApprovalTemplateInput input, {
    List<ApprovalTemplateStepInput> steps = const [],
  }) => throw UnimplementedError();

  @override
  Future<ApprovalTemplate> update(
    int id,
    ApprovalTemplateInput input, {
    List<ApprovalTemplateStepInput>? steps,
  }) => throw UnimplementedError();

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

  @override
  Future<ApprovalTemplate> restore(int id) => throw UnimplementedError();
}
