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/api_error.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_draft.dart';
import 'package:superport_v2/features/approvals/domain/usecases/get_approval_draft_use_case.dart';
import 'package:superport_v2/features/approvals/domain/usecases/list_approval_drafts_use_case.dart';
import 'package:superport_v2/features/approvals/domain/usecases/save_approval_draft_use_case.dart';
import 'package:superport_v2/features/inventory/inbound/presentation/controllers/inbound_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';
import 'package:superport_v2/features/inventory/lookups/domain/repositories/inventory_lookup_repository.dart';
import 'package:superport_v2/features/inventory/transactions/presentation/services/transaction_detail_sync_service.dart';

class _MockStockTransactionRepository extends Mock
    implements StockTransactionRepository {}

class _MockInventoryLookupRepository extends Mock
    implements InventoryLookupRepository {}

class _MockTransactionLineRepository extends Mock
    implements TransactionLineRepository {}

class _MockTransactionCustomerRepository extends Mock
    implements TransactionCustomerRepository {}

class _MockSaveApprovalDraftUseCase extends Mock
    implements SaveApprovalDraftUseCase {}

class _MockGetApprovalDraftUseCase extends Mock
    implements GetApprovalDraftUseCase {}

class _MockListApprovalDraftsUseCase extends Mock
    implements ListApprovalDraftsUseCase {}

void main() {
  group('InboundController', () {
    late StockTransactionRepository transactionRepository;
    late InventoryLookupRepository lookupRepository;
    late TransactionLineRepository lineRepository;
    late TransactionCustomerRepository customerRepository;
    late InboundController controller;

    setUpAll(() {
      registerFallbackValue(
        StockTransactionCreateInput(
          transactionTypeId: 0,
          transactionStatusId: 0,
          warehouseId: 0,
          transactionDate: DateTime(2000, 1, 1),
          createdById: 0,
          approval: StockTransactionApprovalInput(requestedById: 0),
        ),
      );
      registerFallbackValue(
        StockTransactionUpdateInput(transactionStatusId: 0),
      );
      registerFallbackValue(StockTransactionListFilter());
      registerFallbackValue(TransactionCustomerCreateInput(customerId: 0));
      registerFallbackValue(TransactionCustomerUpdateInput(id: 0));
      registerFallbackValue(<TransactionCustomerCreateInput>[]);
      registerFallbackValue(<TransactionCustomerUpdateInput>[]);
      registerFallbackValue(const ApprovalDraftListFilter(requesterId: 0));
      registerFallbackValue(
        ApprovalDraftSaveInput(
          requesterId: 0,
          steps: [ApprovalDraftStep(stepOrder: 0, approverId: 0)],
        ),
      );
    });

    setUp(() {
      transactionRepository = _MockStockTransactionRepository();
      lookupRepository = _MockInventoryLookupRepository();
      lineRepository = _MockTransactionLineRepository();
      customerRepository = _MockTransactionCustomerRepository();
      controller = InboundController(
        transactionRepository: transactionRepository,
        lineRepository: lineRepository,
        customerRepository: customerRepository,
        lookupRepository: lookupRepository,
      );
    });

    test('createTransaction은 레코드를 추가하고 결과를 반환한다', () async {
      final transaction = _buildTransaction();
      when(
        () => transactionRepository.create(any()),
      ).thenAnswer((_) async => transaction);

      final input = StockTransactionCreateInput(
        transactionTypeId: 1,
        transactionStatusId: 2,
        warehouseId: 3,
        transactionDate: DateTime(2024, 3, 1),
        createdById: 9,
        approval: StockTransactionApprovalInput(requestedById: 9),
      );

      final record = await controller.createTransaction(
        input,
        refreshAfter: false,
      );

      expect(record.id, equals(transaction.id));
      expect(controller.records.length, equals(1));
      expect(controller.records.first.id, equals(transaction.id));
      expect(controller.approvalDraft?.requestedById, equals(9));
      verify(() => transactionRepository.create(any())).called(1);
    });

    test('updateTransaction은 로컬 레코드를 갱신한다', () async {
      final original = _buildTransaction();
      final updated = _buildTransaction(statusName: '승인완료');
      when(
        () => transactionRepository.create(any()),
      ).thenAnswer((_) async => original);
      when(
        () => transactionRepository.update(any(), any()),
      ).thenAnswer((_) async => updated);

      await controller.createTransaction(
        StockTransactionCreateInput(
          transactionTypeId: 1,
          transactionStatusId: 2,
          warehouseId: 3,
          transactionDate: DateTime(2024, 3, 1),
          createdById: 9,
          approval: StockTransactionApprovalInput(requestedById: 9),
        ),
        refreshAfter: false,
      );

      final future = controller.updateTransaction(
        original.id!,
        StockTransactionUpdateInput(transactionStatusId: 3),
        refreshAfter: false,
      );

      expect(controller.processingTransactionIds.contains(original.id), isTrue);

      final record = await future;

      expect(record.status, equals('승인완료'));
      expect(controller.records.first.status, equals('승인완료'));
      expect(
        controller.processingTransactionIds.contains(original.id),
        isFalse,
      );
    });

    test('deleteTransaction은 레코드를 제거한다', () async {
      final transaction = _buildTransaction();
      when(
        () => transactionRepository.create(any()),
      ).thenAnswer((_) async => transaction);
      when(
        () => transactionRepository.delete(any()),
      ).thenAnswer((_) async => Future<void>.value());

      await controller.createTransaction(
        StockTransactionCreateInput(
          transactionTypeId: 1,
          transactionStatusId: 2,
          warehouseId: 3,
          transactionDate: DateTime(2024, 3, 1),
          createdById: 9,
          approval: StockTransactionApprovalInput(requestedById: 9),
        ),
        refreshAfter: false,
      );

      final future = controller.deleteTransaction(
        transaction.id!,
        refreshAfter: false,
      );

      expect(
        controller.processingTransactionIds.contains(transaction.id),
        isTrue,
      );

      await future;

      expect(controller.records, isEmpty);
      expect(
        controller.processingTransactionIds.contains(transaction.id),
        isFalse,
      );
      verify(() => transactionRepository.delete(transaction.id!)).called(1);
    });

    test('syncTransactionCustomers는 계획에 따라 저장소를 호출한다', () async {
      when(
        () => customerRepository.updateCustomers(any(), any()),
      ).thenAnswer((_) async {});
      when(
        () => customerRepository.deleteCustomer(any()),
      ).thenAnswer((_) async {});
      when(
        () => customerRepository.addCustomers(any(), any()),
      ).thenAnswer((_) async {});

      final plan = TransactionCustomerSyncPlan(
        createdCustomers: [TransactionCustomerCreateInput(customerId: 7)],
        updatedCustomers: [TransactionCustomerUpdateInput(id: 2, note: '메모')],
        deletedCustomerIds: const [5],
      );

      await controller.syncTransactionCustomers(42, plan);

      verify(() => customerRepository.updateCustomers(42, any())).called(1);
      verify(() => customerRepository.deleteCustomer(5)).called(1);
      verify(() => customerRepository.addCustomers(42, any())).called(1);
    });

    test('updateApprovalDraft와 clearApprovalDraft가 초안을 관리한다', () {
      final approval = StockTransactionApprovalInput(
        requestedById: 55,
        approvalStatusId: 7,
        note: '메모',
      );

      controller.updateApprovalDraft(approval);

      expect(controller.approvalDraft?.requestedById, equals(55));
      expect(controller.approvalDraft?.approvalStatusId, equals(7));

      controller.clearApprovalDraft();

      expect(controller.approvalDraft, isNull);
    });

    test('updateApprovalDraft는 서버 초안을 동기화한다', () async {
      final saveUseCase = _MockSaveApprovalDraftUseCase();
      when(() => saveUseCase.call(any())).thenAnswer(
        (_) async => ApprovalDraftDetail(
          id: 1,
          requesterId: 3,
          savedAt: DateTime.utc(2025, 1, 1),
          payload: ApprovalDraftPayload(steps: const []),
        ),
      );
      final controllerWithSave = InboundController(
        transactionRepository: transactionRepository,
        lineRepository: lineRepository,
        customerRepository: customerRepository,
        lookupRepository: lookupRepository,
        saveDraftUseCase: saveUseCase,
      );

      final approval = StockTransactionApprovalInput(
        requestedById: 3,
        approvalStatusId: 2,
        steps: [ApprovalStepAssignmentItem(stepOrder: 1, approverId: 4)],
      );

      controllerWithSave.updateApprovalDraft(approval);
      await Future<void>.delayed(Duration.zero);

      verify(() => saveUseCase.call(captureAny())).called(1);
    });

    test('loadApprovalDraftFromServer는 원격 초안을 반영한다', () async {
      final listUseCase = _MockListApprovalDraftsUseCase();
      final getUseCase = _MockGetApprovalDraftUseCase();
      final summary = ApprovalDraftSummary(
        id: 5,
        requesterId: 3,
        status: ApprovalDraftStatus.active,
        savedAt: DateTime.utc(2025, 1, 2),
        sessionKey: 'inventory_inbound_3',
        stepCount: 1,
      );
      when(() => listUseCase.call(any())).thenAnswer(
        (_) async => PaginatedResult<ApprovalDraftSummary>(
          items: [summary],
          page: 1,
          pageSize: 10,
          total: 1,
        ),
      );
      final detail = ApprovalDraftDetail(
        id: summary.id,
        requesterId: 3,
        savedAt: DateTime.utc(2025, 1, 2),
        payload: ApprovalDraftPayload(
          metadata: const {
            '_client_state': {'status_id': 6},
          },
          steps: [ApprovalDraftStep(stepOrder: 1, approverId: 9)],
        ),
      );
      when(
        () => getUseCase.call(id: summary.id, requesterId: 3),
      ).thenAnswer((_) async => detail);

      final controllerWithDrafts = InboundController(
        transactionRepository: transactionRepository,
        lineRepository: lineRepository,
        customerRepository: customerRepository,
        lookupRepository: lookupRepository,
        getDraftUseCase: getUseCase,
        listDraftsUseCase: listUseCase,
      );

      await controllerWithDrafts.loadApprovalDraftFromServer(requesterId: 3);

      verify(() => listUseCase.call(any())).called(1);
      verify(() => getUseCase.call(id: summary.id, requesterId: 3)).called(1);
      final approval = controllerWithDrafts.approvalDraft;
      expect(approval, isNotNull);
      expect(approval!.requestedById, 3);
      expect(approval.approvalStatusId, 6);
      expect(approval.steps, hasLength(1));
    });

    test('submitTransaction은 refreshAfter가 true일 때 목록을 다시 불러온다', () async {
      final filter = StockTransactionListFilter(transactionTypeId: 1);
      final initial = _buildTransaction();
      when(
        () => transactionRepository.list(filter: any(named: 'filter')),
      ).thenAnswer(
        (_) async => PaginatedResult<StockTransaction>(
          items: [initial],
          page: 1,
          pageSize: 20,
          total: 1,
        ),
      );
      await controller.fetchTransactions(filter: filter);

      final updated = _buildTransaction(statusName: '승인대기');
      when(
        () => transactionRepository.submit(any(), note: any(named: 'note')),
      ).thenAnswer((_) async => updated);
      when(
        () => transactionRepository.list(filter: any(named: 'filter')),
      ).thenAnswer(
        (_) async => PaginatedResult<StockTransaction>(
          items: [updated],
          page: 1,
          pageSize: 20,
          total: 1,
        ),
      );

      final result = await controller.submitTransaction(initial.id!);

      expect(result.status, equals('승인대기'));
      expect(controller.records.first.status, equals('승인대기'));
      verify(
        () =>
            transactionRepository.submit(initial.id!, note: any(named: 'note')),
      ).called(1);
    });

    test('fetchTransactions 실패 시 Failure 메시지를 노출한다', () async {
      final exception = ApiException(
        code: ApiErrorCode.unprocessableEntity,
        message: '입고 목록을 불러오지 못했습니다.',
        details: {
          'errors': {
            'transaction_date': ['처리일자를 선택해 주세요.'],
          },
        },
      );

      when(
        () => transactionRepository.list(filter: any(named: 'filter')),
      ).thenThrow(exception);

      await controller.fetchTransactions(
        filter: StockTransactionListFilter(transactionTypeId: 1),
      );

      final failure = Failure.from(exception);
      expect(controller.errorMessage, equals(failure.describe()));
      expect(controller.records, isEmpty);
    });
  });
}

StockTransaction _buildTransaction({int id = 100, String statusName = '작성중'}) {
  return StockTransaction(
    id: id,
    transactionNo: 'TX-$id',
    transactionDate: DateTime(2024, 3, 1),
    type: StockTransactionType(id: 10, name: '입고'),
    status: StockTransactionStatus(id: 11, name: statusName),
    warehouse: StockTransactionWarehouse(id: 1, code: 'WH', name: '서울 물류'),
    createdBy: StockTransactionEmployee(
      id: 1,
      employeeNo: 'EMP-1',
      name: '관리자',
    ),
    lines: [
      StockTransactionLine(
        id: 1,
        lineNo: 1,
        product: StockTransactionProduct(id: 1, code: 'P-1', name: '테스트 상품'),
        quantity: 5,
        unitPrice: 1000.0,
      ),
    ],
    customers: const [],
  );
}
