import 'package:dio/dio.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';

import 'package:superport_v2/core/network/api_client.dart';
import 'package:superport_v2/features/inventory/transactions/data/repositories/stock_transaction_repository_remote.dart';
import 'package:superport_v2/features/inventory/transactions/domain/entities/stock_transaction_input.dart';

class _MockApiClient extends Mock implements ApiClient {}

void main() {
  late ApiClient apiClient;
  late StockTransactionRepositoryRemote repository;

  setUpAll(() {
    registerFallbackValue(Options());
    registerFallbackValue(CancelToken());
  });

  setUp(() {
    apiClient = _MockApiClient();
    repository = StockTransactionRepositoryRemote(apiClient: apiClient);
  });

  Future<Response<Map<String, dynamic>>> emptyListResponse(String path) async {
    return Response<Map<String, dynamic>>(
      data: {'items': const [], 'page': 1, 'page_size': 20, 'total': 0},
      requestOptions: RequestOptions(path: path),
      statusCode: 200,
    );
  }

  Map<String, dynamic> detailBody() {
    return {
      'data': {
        'id': 10,
        'transaction_no': 'TX-1',
        'transaction_type': {'id': 1, 'type_name': '입고'},
        'transaction_status': {'id': 1, 'status_name': '작성중'},
        'warehouse': {'id': 3, 'warehouse_code': 'W-1', 'warehouse_name': '서울'},
        'created_by': {'id': 5, 'employee_no': 'EMP-1', 'employee_name': '홍길동'},
        'lines': const [],
        'customers': const [],
      },
    };
  }

  test('list 호출 시 필터 파라미터를 전달한다', () async {
    const path = '/api/v1/stock-transactions';
    when(
      () => apiClient.get<Map<String, dynamic>>(
        path,
        query: any(named: 'query'),
        options: any(named: 'options'),
        cancelToken: any(named: 'cancelToken'),
      ),
    ).thenAnswer((_) => emptyListResponse(path));

    final filter = StockTransactionListFilter(
      page: 2,
      pageSize: 50,
      query: '품번',
      transactionTypeId: 11,
      transactionStatusId: 7,
      warehouseId: 3,
      customerId: 99,
      from: DateTime(2024, 1, 1),
      to: DateTime(2024, 1, 31),
      sort: 'transaction_date',
      order: 'desc',
      include: const ['lines', 'approval'],
    );

    await repository.list(filter: filter);

    final captured = verify(
      () => apiClient.get<Map<String, dynamic>>(
        captureAny(),
        query: captureAny(named: 'query'),
        options: any(named: 'options'),
        cancelToken: any(named: 'cancelToken'),
      ),
    ).captured;

    final query = captured[1] as Map<String, dynamic>;
    expect(captured.first, equals(path));
    expect(query['page'], 2);
    expect(query['page_size'], 50);
    expect(query['q'], '품번');
    expect(query['transaction_type_id'], 11);
    expect(query['transaction_status_id'], 7);
    expect(query['warehouse_id'], 3);
    expect(query['customer_id'], 99);
    expect(query['date_from'], '2024-01-01');
    expect(query['date_to'], '2024-01-31');
    expect(query['sort'], 'transaction_date');
    expect(query['order'], 'desc');
    expect(query['include'], 'lines,approval');
  });

  test('fetchDetail은 include 파라미터를 조인해 전달한다', () async {
    const path = '/api/v1/stock-transactions/10';
    when(
      () => apiClient.get<Map<String, dynamic>>(
        path,
        query: any(named: 'query'),
        options: any(named: 'options'),
        cancelToken: any(named: 'cancelToken'),
      ),
    ).thenAnswer(
      (_) async => Response<Map<String, dynamic>>(
        data: detailBody(),
        requestOptions: RequestOptions(path: path),
        statusCode: 200,
      ),
    );

    await repository.fetchDetail(10, include: const ['lines', 'customers']);

    final query =
        verify(
              () => apiClient.get<Map<String, dynamic>>(
                captureAny(),
                query: captureAny(named: 'query'),
                options: any(named: 'options'),
                cancelToken: any(named: 'cancelToken'),
              ),
            ).captured[1]
            as Map<String, dynamic>;

    expect(query['include'], 'lines,customers');
  });

  test('create는 입력 payload를 body로 전달한다', () async {
    const path = '/api/v1/stock-transactions';
    when(
      () => apiClient.post<Map<String, dynamic>>(
        path,
        data: any(named: 'data'),
        options: any(named: 'options'),
        cancelToken: any(named: 'cancelToken'),
      ),
    ).thenAnswer(
      (_) async => Response<Map<String, dynamic>>(
        data: detailBody(),
        requestOptions: RequestOptions(path: path),
        statusCode: 201,
      ),
    );

    final input = StockTransactionCreateInput(
      transactionTypeId: 1,
      transactionStatusId: 2,
      warehouseId: 3,
      transactionDate: DateTime(2024, 2, 1),
      createdById: 9,
      note: '테스트',
      lines: [
        TransactionLineCreateInput(
          lineNo: 1,
          productId: 11,
          quantity: 2,
          unitPrice: 1000,
        ),
      ],
      customers: [TransactionCustomerCreateInput(customerId: 7)],
      approval: StockTransactionApprovalInput(
        requestedById: 9,
        approvalStatusId: 5,
        note: '승인 요청',
      ),
    );

    await repository.create(input);

    final payload =
        verify(
              () => apiClient.post<Map<String, dynamic>>(
                captureAny(),
                data: captureAny(named: 'data'),
                options: any(named: 'options'),
                cancelToken: any(named: 'cancelToken'),
              ),
            ).captured[1]
            as Map<String, dynamic>;

    expect(payload['transaction_type_id'], 1);
    expect(payload['transaction_status_id'], 2);
    expect(payload['warehouse_id'], 3);
    expect(payload['created_by_id'], 9);
    expect(payload['lines'], isA<List>());
    expect(payload['customers'], isA<List>());
    expect(payload.containsKey('transaction_no'), isFalse);
    final approvalPayload = payload['approval'] as Map<String, dynamic>;
    expect(approvalPayload.containsKey('approval_no'), isFalse);
    expect(approvalPayload['requested_by_id'], 9);
    expect(approvalPayload['approval_status_id'], 5);
    expect(approvalPayload['note'], '승인 요청');
  });

  test('submit은 /submit 엔드포인트를 호출한다', () async {
    const path = '/api/v1/stock-transactions/10/submit';
    when(
      () => apiClient.post<Map<String, dynamic>>(
        path,
        data: any(named: 'data'),
        options: any(named: 'options'),
        cancelToken: any(named: 'cancelToken'),
      ),
    ).thenAnswer(
      (_) async => Response<Map<String, dynamic>>(
        data: detailBody(),
        requestOptions: RequestOptions(path: path),
        statusCode: 200,
      ),
    );

    await repository.submit(10, note: '승인 요청');

    final payload =
        verify(
              () => apiClient.post<Map<String, dynamic>>(
                path,
                data: captureAny(named: 'data'),
                options: any(named: 'options'),
                cancelToken: any(named: 'cancelToken'),
              ),
            ).captured[0]
            as Map<String, dynamic>;
    expect(payload['id'], 10);
    expect(payload['note'], '승인 요청');
  });

  test('complete는 /complete 엔드포인트를 호출한다', () async {
    const path = '/api/v1/stock-transactions/10/complete';
    when(
      () => apiClient.post<Map<String, dynamic>>(
        path,
        data: any(named: 'data'),
        options: any(named: 'options'),
        cancelToken: any(named: 'cancelToken'),
      ),
    ).thenAnswer(
      (_) async => Response<Map<String, dynamic>>(
        data: detailBody(),
        requestOptions: RequestOptions(path: path),
        statusCode: 200,
      ),
    );

    await repository.complete(10, note: '처리 완료');

    final payload =
        verify(
              () => apiClient.post<Map<String, dynamic>>(
                path,
                data: captureAny(named: 'data'),
                options: any(named: 'options'),
                cancelToken: any(named: 'cancelToken'),
              ),
            ).captured[0]
            as Map<String, dynamic>;
    expect(payload['id'], 10);
    expect(payload['note'], '처리 완료');
  });

  test('approve는 /approve 엔드포인트를 호출한다', () async {
    const path = '/api/v1/stock-transactions/11/approve';
    when(
      () => apiClient.post<Map<String, dynamic>>(
        path,
        data: any(named: 'data'),
        options: any(named: 'options'),
        cancelToken: any(named: 'cancelToken'),
      ),
    ).thenAnswer(
      (_) async => Response<Map<String, dynamic>>(
        data: detailBody(),
        requestOptions: RequestOptions(path: path),
        statusCode: 200,
      ),
    );

    await repository.approve(11, note: '승인 확정');

    final payload =
        verify(
              () => apiClient.post<Map<String, dynamic>>(
                path,
                data: captureAny(named: 'data'),
                options: any(named: 'options'),
                cancelToken: any(named: 'cancelToken'),
              ),
            ).captured[0]
            as Map<String, dynamic>;
    expect(payload['id'], 11);
    expect(payload['note'], '승인 확정');
  });

  test('reject는 /reject 엔드포인트를 호출한다', () async {
    const path = '/api/v1/stock-transactions/12/reject';
    when(
      () => apiClient.post<Map<String, dynamic>>(
        path,
        data: any(named: 'data'),
        options: any(named: 'options'),
        cancelToken: any(named: 'cancelToken'),
      ),
    ).thenAnswer(
      (_) async => Response<Map<String, dynamic>>(
        data: detailBody(),
        requestOptions: RequestOptions(path: path),
        statusCode: 200,
      ),
    );

    await repository.reject(12, note: '재작업 필요');

    final payload =
        verify(
              () => apiClient.post<Map<String, dynamic>>(
                path,
                data: captureAny(named: 'data'),
                options: any(named: 'options'),
                cancelToken: any(named: 'cancelToken'),
              ),
            ).captured[0]
            as Map<String, dynamic>;
    expect(payload['id'], 12);
    expect(payload['note'], '재작업 필요');
  });

  test('cancel은 /cancel 엔드포인트를 호출한다', () async {
    const path = '/api/v1/stock-transactions/13/cancel';
    when(
      () => apiClient.post<Map<String, dynamic>>(
        path,
        data: any(named: 'data'),
        options: any(named: 'options'),
        cancelToken: any(named: 'cancelToken'),
      ),
    ).thenAnswer(
      (_) async => Response<Map<String, dynamic>>(
        data: detailBody(),
        requestOptions: RequestOptions(path: path),
        statusCode: 200,
      ),
    );

    await repository.cancel(13, note: '상신 취소');

    final payload =
        verify(
              () => apiClient.post<Map<String, dynamic>>(
                path,
                data: captureAny(named: 'data'),
                options: any(named: 'options'),
                cancelToken: any(named: 'cancelToken'),
              ),
            ).captured[0]
            as Map<String, dynamic>;
    expect(payload['id'], 13);
    expect(payload['note'], '상신 취소');
  });

  test('transition note는 공백 입력 시 제외된다', () async {
    const path = '/api/v1/stock-transactions/99/submit';
    when(
      () => apiClient.post<Map<String, dynamic>>(
        path,
        data: any(named: 'data'),
        options: any(named: 'options'),
        cancelToken: any(named: 'cancelToken'),
      ),
    ).thenAnswer(
      (_) async => Response<Map<String, dynamic>>(
        data: detailBody(),
        requestOptions: RequestOptions(path: path),
        statusCode: 200,
      ),
    );

    await repository.submit(99, note: '   ');

    final payload =
        verify(
              () => apiClient.post<Map<String, dynamic>>(
                path,
                data: captureAny(named: 'data'),
                options: any(named: 'options'),
                cancelToken: any(named: 'cancelToken'),
              ),
            ).captured[0]
            as Map<String, dynamic>;
    expect(payload['id'], 99);
    expect(payload.containsKey('note'), isFalse);
  });
}
