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/features/approvals/domain/entities/approval.dart';
import 'package:superport_v2/features/approvals/step/domain/entities/approval_step_input.dart';
import 'package:superport_v2/features/approvals/step/domain/entities/approval_step_record.dart';
import 'package:superport_v2/features/approvals/step/domain/repositories/approval_step_repository.dart';
import 'package:superport_v2/features/approvals/step/presentation/controllers/approval_step_controller.dart';

class _MockApprovalStepRepository extends Mock
    implements ApprovalStepRepository {}

void main() {
  setUpAll(() {
    registerFallbackValue(
      ApprovalStepInput(approvalId: 1, stepOrder: 1, approverId: 1),
    );
  });

  late ApprovalStepController controller;
  late _MockApprovalStepRepository repository;

  final sampleRecord = ApprovalStepRecord(
    approvalId: 10,
    approvalNo: 'APP-2024-0001',
    transactionNo: 'TRX-2024-01',
    templateName: '입고 기본',
    step: ApprovalStep(
      id: 100,
      stepOrder: 1,
      approver: ApprovalApprover(id: 21, employeeNo: 'E001', name: '최승인'),
      status: ApprovalStatus(id: 1, name: '승인대기', color: null),
      assignedAt: DateTime(2024, 4, 1, 9),
      decidedAt: null,
      note: '확인 요청',
    ),
  );

  PaginatedResult<ApprovalStepRecord> createResult(
    List<ApprovalStepRecord> items,
  ) {
    return PaginatedResult<ApprovalStepRecord>(
      items: items,
      page: 1,
      pageSize: 20,
      total: items.length,
    );
  }

  setUp(() {
    repository = _MockApprovalStepRepository();
    controller = ApprovalStepController(repository: repository);
  });

  test('fetch 성공 시 결과를 갱신한다', () async {
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        statusId: any(named: 'statusId'),
        approverId: any(named: 'approverId'),
        approvalId: any(named: 'approvalId'),
      ),
    ).thenAnswer((_) async => createResult([sampleRecord]));

    await controller.fetch();

    expect(controller.result?.items, isNotEmpty);
    expect(controller.errorMessage, isNull);
    verify(
      () => repository.list(
        page: 1,
        pageSize: 20,
        query: null,
        statusId: null,
        approverId: null,
        approvalId: null,
      ),
    ).called(1);
  });

  test('에러 발생 시 errorMessage를 설정한다', () async {
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        statusId: any(named: 'statusId'),
        approverId: any(named: 'approverId'),
        approvalId: any(named: 'approvalId'),
      ),
    ).thenThrow(Exception('fail'));

    await controller.fetch();

    expect(controller.errorMessage, isNotNull);
    expect(controller.result, isNull);
  });

  test('필터 갱신 후 fetch 시 파라미터에 반영한다', () async {
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        statusId: any(named: 'statusId'),
        approverId: any(named: 'approverId'),
        approvalId: any(named: 'approvalId'),
      ),
    ).thenAnswer((_) async => createResult([sampleRecord]));

    controller.updateQuery('APP-2024');
    controller.updateStatusId(2);
    controller.updateApproverId(21);

    await controller.fetch(page: 3);

    verify(
      () => repository.list(
        page: 3,
        pageSize: 20,
        query: 'APP-2024',
        statusId: 2,
        approverId: 21,
        approvalId: null,
      ),
    ).called(1);
  });

  test('fetchDetail 성공 시 selected가 설정된다', () async {
    when(
      () => repository.fetchDetail(any()),
    ).thenAnswer((_) async => sampleRecord);

    final detail = await controller.fetchDetail(100);

    expect(detail, isNotNull);
    expect(controller.selected, isNotNull);
    verify(() => repository.fetchDetail(100)).called(1);
  });

  test('fetchDetail 실패 시 null을 반환한다', () async {
    when(() => repository.fetchDetail(any())).thenThrow(Exception('fail'));

    final detail = await controller.fetchDetail(100);

    expect(detail, isNull);
    expect(controller.errorMessage, isNotNull);
  });

  test('createStep 성공 시 생성된 레코드를 반환한다', () async {
    when(() => repository.create(any())).thenAnswer((_) async => sampleRecord);

    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        statusId: any(named: 'statusId'),
        approverId: any(named: 'approverId'),
        approvalId: any(named: 'approvalId'),
      ),
    ).thenAnswer((_) async => createResult([sampleRecord]));

    final result = await controller.createStep(
      ApprovalStepInput(approvalId: 10, stepOrder: 1, approverId: 21),
    );

    expect(result, isNotNull);
    expect(controller.errorMessage, isNull);
    verify(() => repository.create(any())).called(1);
  });

  test('createStep 실패 시 null을 반환하고 에러를 기록한다', () async {
    when(() => repository.create(any())).thenThrow(Exception('fail'));

    final result = await controller.createStep(
      ApprovalStepInput(approvalId: 10, stepOrder: 1, approverId: 21),
    );

    expect(result, isNull);
    expect(controller.errorMessage, isNotNull);
  });

  test('updateStep 성공 시 수정된 레코드를 반환한다', () async {
    when(
      () => repository.update(any(), any()),
    ).thenAnswer((_) async => sampleRecord);

    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        statusId: any(named: 'statusId'),
        approverId: any(named: 'approverId'),
        approvalId: any(named: 'approvalId'),
      ),
    ).thenAnswer((_) async => createResult([sampleRecord]));

    final result = await controller.updateStep(
      100,
      ApprovalStepInput(stepOrder: 2, approverId: 25, approvalId: 10),
    );

    expect(result, isNotNull);
    expect(controller.errorMessage, isNull);
    verify(() => repository.update(100, any())).called(1);
  });

  test('updateStep 실패 시 null을 반환한다', () async {
    when(() => repository.update(any(), any())).thenThrow(Exception('fail'));

    final result = await controller.updateStep(
      100,
      ApprovalStepInput(stepOrder: 2, approverId: 25, approvalId: 10),
    );

    expect(result, isNull);
    expect(controller.errorMessage, isNotNull);
  });

  test('deleteStep 성공 시 단계가 비활성화된다', () async {
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        statusId: any(named: 'statusId'),
        approverId: any(named: 'approverId'),
        approvalId: any(named: 'approvalId'),
      ),
    ).thenAnswer((_) async => createResult([sampleRecord]));

    when(() => repository.delete(any())).thenAnswer((_) async {});

    await controller.fetch();

    final success = await controller.deleteStep(sampleRecord.step.id!);

    expect(success, isTrue);
    expect(controller.result?.items.first.step.isDeleted, isTrue);
  });

  test('deleteStep 실패 시 false를 반환하고 에러를 기록한다', () async {
    when(() => repository.delete(any())).thenThrow(Exception('fail'));

    final success = await controller.deleteStep(999);

    expect(success, isFalse);
    expect(controller.errorMessage, isNotNull);
  });

  test('restoreStep 성공 시 단계가 활성화된다', () async {
    final deletedRecord = sampleRecord.copyWith(
      step: sampleRecord.step.copyWith(isDeleted: true),
    );

    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        statusId: any(named: 'statusId'),
        approverId: any(named: 'approverId'),
        approvalId: any(named: 'approvalId'),
      ),
    ).thenAnswer((_) async => createResult([deletedRecord]));

    when(() => repository.restore(any())).thenAnswer((_) async => sampleRecord);

    await controller.fetch();

    final restored = await controller.restoreStep(sampleRecord.step.id!);

    expect(restored, isNotNull);
    expect(controller.result?.items.first.step.isDeleted, isFalse);
  });

  test('restoreStep 실패 시 null을 반환하고 에러를 기록한다', () async {
    when(() => repository.restore(any())).thenThrow(Exception('fail'));

    final restored = await controller.restoreStep(100);

    expect(restored, isNull);
    expect(controller.errorMessage, isNotNull);
  });
}
