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/approvals/data/repositories/approval_repository_remote.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval.dart';

class _MockApiClient extends Mock implements ApiClient {}

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

  setUpAll(() {
    registerFallbackValue(Options());
    registerFallbackValue(CancelToken());
    registerFallbackValue(
      Response<dynamic>(requestOptions: RequestOptions(path: '/')),
    );
  });

  setUp(() {
    apiClient = _MockApiClient();
    repository = ApprovalRepositoryRemote(apiClient: apiClient);
    when(() => apiClient.unwrapAsMap(any())).thenAnswer((invocation) {
      final response = invocation.positionalArguments.first;
      if (response is Response) {
        final data = response.data;
        if (data is Map<String, dynamic>) {
          final nested = data['data'];
          if (nested is Map<String, dynamic>) {
            return nested;
          }
          return data;
        }
      }
      return <String, dynamic>{};
    });
  });

  test('list는 신규 필터 파라미터를 전달한다', () async {
    const path = '/api/v1/approvals';
    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: {'items': const [], 'page': 1, 'page_size': 20, 'total': 0},
        statusCode: 200,
        requestOptions: RequestOptions(path: path),
      ),
    );

    await repository.list(
      page: 2,
      pageSize: 50,
      transactionId: 10,
      approvalStatusId: 5,
      requestedById: 7,
      includeSteps: true,
      includeHistories: true,
    );

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

    expect(captured.first, equals(path));
    final query = captured[1] as Map<String, dynamic>;
    expect(query['page'], 2);
    expect(query['page_size'], 50);
    expect(query['transaction_id'], 10);
    expect(query['approval_status_id'], 5);
    expect(query['requested_by_id'], 7);
    expect(query['include'], 'requested_by,transaction,steps,histories');
  });

  test('list는 status 코드와 include_pending 옵션을 전달한다', () async {
    const path = '/api/v1/approvals';
    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: {'items': const [], 'page': 1, 'page_size': 20, 'total': 0},
        statusCode: 200,
        requestOptions: RequestOptions(path: path),
      ),
    );

    await repository.list(
      statusCodes: const ['draft', 'submitted'],
      includePending: true,
    );

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

    expect(captured.first, equals(path));
    final query = captured[1] as Map<String, dynamic>;
    expect(query['status'], 'draft,submitted');
    expect(query['include_pending'], true);
    expect(query['include'], 'requested_by,transaction');
  });

  test('create는 필수 필드를 전달한다', () async {
    const path = '/api/v1/approvals';
    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: {
          'data': {
            'id': 5001,
            'approval_no': 'APP-2025-0001',
            'transaction_id': 9001,
          },
        },
        statusCode: 201,
        requestOptions: RequestOptions(path: path),
      ),
    );

    final input = ApprovalCreateInput(
      transactionId: 9001,
      approvalStatusId: 1,
      requestedById: 7,
      note: '  신규 결재  ',
    );

    await repository.create(input);

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

    expect(captured.first, equals(path));
    final payload = captured[1] as Map<String, dynamic>;
    expect(payload['transaction_id'], 9001);
    expect(payload.containsKey('approval_no'), isFalse);
    expect(payload['approval_status_id'], 1);
    expect(payload['requested_by_id'], 7);
    expect(payload['note'], '신규 결재');
  });

  test('update는 id를 포함해 패치를 수행한다', () async {
    const path = '/api/v1/approvals/5001';
    when(
      () => apiClient.patch<Map<String, dynamic>>(
        path,
        data: any(named: 'data'),
        options: any(named: 'options'),
        cancelToken: any(named: 'cancelToken'),
      ),
    ).thenAnswer(
      (_) async => Response<Map<String, dynamic>>(
        data: {
          'data': {
            'id': 5001,
            'approval_no': 'APP-2025-0001',
            'approval_status': {'id': 2, 'status_name': '진행중'},
          },
        },
        statusCode: 200,
        requestOptions: RequestOptions(path: path),
      ),
    );

    final input = ApprovalUpdateInput(
      id: 5001,
      approvalStatusId: 2,
      note: '보류',
    );

    await repository.update(input);

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

    expect(captured.first, equals(path));
    final payload = captured[1] as Map<String, dynamic>;
    expect(payload['id'], 5001);
    expect(payload['approval_status_id'], 2);
    expect(payload['note'], '보류');
  });

  Map<String, dynamic> buildStep({
    required int id,
    required int order,
    required int statusId,
    String statusName = '대기',
  }) {
    return {
      'id': id,
      'approval_id': 5001,
      'step_order': order,
      'approver': {
        'id': 20 + order,
        'employee_no': 'E${order.toString().padLeft(4, '0')}',
        'employee_name': '승인자$order',
      },
      'step_status': {
        'id': statusId,
        'status_name': statusName,
        'is_blocking_next': true,
        'is_terminal': false,
      },
      'assigned_at': '2025-09-18T06:00:00Z',
      'decided_at': order == 1 ? '2025-09-18T08:05:00Z' : null,
      'note': order == 1 ? '승인합니다.' : null,
    };
  }

  Map<String, dynamic> buildHistory({
    required int id,
    required String timestamp,
  }) {
    return {
      'id': id,
      'approval_action_id': 1,
      'approval_action_name': '승인',
      'action_at': timestamp,
      'note': '이력$id',
      'from_status': {
        'id': 1,
        'status_name': '대기',
        'is_blocking_next': true,
        'is_terminal': false,
      },
      'to_status': {
        'id': 2,
        'status_name': '진행중',
        'is_blocking_next': true,
        'is_terminal': false,
      },
    };
  }

  test('performStepAction은 응답의 steps와 histories를 병합한다', () async {
    const path = '/api/v1/approval-steps/7001/actions';
    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: {
          'data': {
            'approval': {
              'id': 5001,
              'approval_no': 'APP-2025-0001',
              'status': {'id': 2, 'status_name': '진행중'},
              'current_step': {'id': 7002, 'step_order': 2},
              'requester': {
                'id': 7,
                'employee_no': 'E0007',
                'employee_name': '김요청',
              },
              'requested_at': '2025-09-18T06:00:00Z',
              'steps': [buildStep(id: 7001, order: 1, statusId: 1)],
              'histories': [
                buildHistory(id: 91000, timestamp: '2025-09-18T07:00:00Z'),
              ],
            },
            'steps': [
              buildStep(id: 7001, order: 1, statusId: 2, statusName: '진행중'),
              buildStep(id: 7002, order: 2, statusId: 1),
            ],
            'step': buildStep(
              id: 7001,
              order: 1,
              statusId: 2,
              statusName: '진행중',
            ),
            'next_step': buildStep(
              id: 7002,
              order: 2,
              statusId: 3,
              statusName: '대기',
            ),
            'history': buildHistory(
              id: 91001,
              timestamp: '2025-09-18T08:05:00Z',
            ),
          },
        },
        statusCode: 200,
        requestOptions: RequestOptions(path: path),
      ),
    );

    final result = await repository.performStepAction(
      ApprovalStepActionInput(stepId: 7001, actionId: 1, note: '승인합니다.'),
    );

    expect(result.id, 5001);
    expect(result.steps.length, 2);

    final firstStep = result.steps.firstWhere((step) => step.id == 7001);
    expect(firstStep.status.id, 2);
    expect(firstStep.status.name, '진행중');

    final secondStep = result.steps.firstWhere((step) => step.id == 7002);
    expect(secondStep.status.id, 3);

    expect(result.histories.length, 2);
    expect(result.histories.last.id, 91001);
  });

  test('fetchDetail은 include 파라미터를 steps,histories로 조합한다', () async {
    const path = '/api/v1/approvals/1';
    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: {
          'data': {
            'id': 1,
            'approval_no': 'AP-1',
            'status': {'id': 1, 'status_name': '대기'},
            'current_step': {'id': 2, 'step_order': 1},
            'requester': {
              'id': 3,
              'employee_no': 'EMP-3',
              'employee_name': '신청자',
            },
            'requested_at': '2024-01-01T00:00:00Z',
            'steps': const [],
            'histories': const [],
          },
        },
        statusCode: 200,
        requestOptions: RequestOptions(path: path),
      ),
    );

    await repository.fetchDetail(1, includeSteps: true, includeHistories: true);

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

    expect(query['include'], 'transaction,requested_by,steps,histories');
  });

  test('listHistory는 날짜 필터를 ISO 문자열로 직렬화한다', () async {
    const path = '/api/v1/approval/history';
    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: {'items': const [], 'page': 1, 'page_size': 20, 'total': 0},
        statusCode: 200,
        requestOptions: RequestOptions(path: path),
      ),
    );

    final from = DateTime.utc(2025, 1, 1, 9);
    final to = DateTime.utc(2025, 1, 2, 12);

    await repository.listHistory(
      approvalId: 99,
      page: 3,
      pageSize: 40,
      from: from,
      to: to,
      actorId: 7,
      approvalActionId: 1,
    );

    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['approval_id'], 99);
    expect(query['page'], 3);
    expect(query['page_size'], 40);
    expect(query['action_from'], from.toUtc().toIso8601String());
    expect(query['action_to'], to.toUtc().toIso8601String());
    expect(query['approver_id'], 7);
    expect(query['approval_action_id'], 1);
  });
}
