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/data/dtos/approval_template_dto.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval_flow.dart';
import 'package:superport_v2/features/approvals/domain/entities/approval_template.dart';
import 'package:superport_v2/features/approvals/domain/repositories/approval_template_repository.dart';
import 'package:superport_v2/features/approvals/domain/usecases/apply_approval_template_use_case.dart';
import 'package:superport_v2/features/approvals/domain/usecases/save_approval_template_use_case.dart';
import 'package:superport_v2/features/approvals/template/presentation/controllers/approval_template_controller.dart';

import '../../../../../helpers/fixture_loader.dart';

class _MockApprovalTemplateRepository extends Mock
    implements ApprovalTemplateRepository {}

class _MockSaveApprovalTemplateUseCase extends Mock
    implements SaveApprovalTemplateUseCase {}

class _MockApplyApprovalTemplateUseCase extends Mock
    implements ApplyApprovalTemplateUseCase {}

class _FakeTemplateInput extends Fake implements ApprovalTemplateInput {}

class _FakeStepInput extends Fake implements ApprovalTemplateStepInput {}

void main() {
  late ApprovalTemplateController controller;
  late _MockApprovalTemplateRepository repository;
  late _MockSaveApprovalTemplateUseCase saveUseCase;
  late _MockApplyApprovalTemplateUseCase applyUseCase;

  final sampleTemplate = ApprovalTemplateDto.fromJson(
    loadJsonFixture('approvals/approval_template_sample.json'),
  ).toEntity();

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

  ApprovalFlow createFlow() {
    final status = ApprovalStatus(id: 10, name: '대기');
    final requester = ApprovalRequester(
      id: 99,
      employeeNo: 'EMP099',
      name: '상신자',
    );
    final approver = ApprovalApprover(
      id: 100,
      employeeNo: 'EMP100',
      name: '승인자',
    );
    final step = ApprovalStep(
      stepOrder: 1,
      approver: approver,
      status: status,
      assignedAt: DateTime(2024, 4, 1),
    );
    final approval = Approval(
      id: 7,
      approvalNo: 'APP-20240401-0001',
      transactionNo: 'TRX-001',
      status: status,
      requester: requester,
      requestedAt: DateTime(2024, 4, 1),
      steps: [step],
    );
    return ApprovalFlow(approval: approval);
  }

  setUpAll(() {
    registerFallbackValue(_FakeTemplateInput());
    registerFallbackValue(_FakeStepInput());
    registerFallbackValue(<ApprovalTemplateStepInput>[]);
  });

  setUp(() {
    repository = _MockApprovalTemplateRepository();
    saveUseCase = _MockSaveApprovalTemplateUseCase();
    applyUseCase = _MockApplyApprovalTemplateUseCase();
    controller = ApprovalTemplateController(
      repository: repository,
      saveTemplateUseCase: saveUseCase,
      applyTemplateUseCase: applyUseCase,
    );
  });

  group('fetch', () {
    setUp(() {
      when(
        () => repository.list(
          page: any(named: 'page'),
          pageSize: any(named: 'pageSize'),
          query: any(named: 'query'),
          isActive: any(named: 'isActive'),
        ),
      ).thenAnswer((_) async => createResult([sampleTemplate]));
    });

    test('목록을 조회하고 버전을 캐시한다', () async {
      await controller.fetch();

      expect(controller.result?.items, isNotEmpty);
      expect(controller.errorMessage, isNull);
      expect(controller.versionOf(sampleTemplate.id), sampleTemplate.updatedAt);
    });

    test('필터를 전달한다', () async {
      controller.updateQuery('AP_');
      controller.updateStatusFilter(ApprovalTemplateStatusFilter.inactiveOnly);

      await controller.fetch(page: 2);

      verify(
        () => repository.list(
          page: 2,
          pageSize: 20,
          query: 'AP_',
          isActive: false,
        ),
      ).called(1);
    });

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

      await controller.fetch();

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

  test('create 호출 시 SaveUseCase를 사용하고 버전을 기록한다', () async {
    when(
      () => saveUseCase.call(
        templateId: any(named: 'templateId'),
        input: any(named: 'input'),
        steps: any(named: 'steps'),
      ),
    ).thenAnswer((_) async => sampleTemplate);
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        isActive: any(named: 'isActive'),
      ),
    ).thenAnswer((_) async => createResult([sampleTemplate]));

    final created = await controller.create(
      ApprovalTemplateInput(code: 'AP_INBOUND', name: '입고 결재 기본'),
      [ApprovalTemplateStepInput(stepOrder: 1, approverId: 10)],
    );

    expect(created, isNotNull);
    expect(controller.versionOf(sampleTemplate.id), sampleTemplate.updatedAt);
    verify(
      () => saveUseCase.call(
        templateId: any(named: 'templateId'),
        input: any(named: 'input'),
        steps: any(named: 'steps'),
      ),
    ).called(1);
  });

  test('save는 update 경로에서도 유즈케이스를 사용한다', () async {
    when(
      () => saveUseCase.call(
        templateId: any(named: 'templateId'),
        input: any(named: 'input'),
        steps: any(named: 'steps'),
      ),
    ).thenAnswer((_) async => sampleTemplate);
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        isActive: any(named: 'isActive'),
      ),
    ).thenAnswer((_) async => createResult([sampleTemplate]));

    await controller.save(
      templateId: 1,
      input: ApprovalTemplateInput(name: '수정 템플릿'),
      steps: const [],
    );

    verify(
      () => saveUseCase.call(
        templateId: 1,
        input: any(named: 'input'),
        steps: any(named: 'steps'),
      ),
    ).called(1);
  });

  test('유즈케이스 미주입 시 저장소를 직접 호출한다', () async {
    final fallbackController = ApprovalTemplateController(
      repository: repository,
    );
    when(
      () => repository.create(any(), steps: any(named: 'steps')),
    ).thenAnswer((_) async => sampleTemplate);
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        isActive: any(named: 'isActive'),
      ),
    ).thenAnswer((_) async => createResult([sampleTemplate]));

    await fallbackController.create(
      ApprovalTemplateInput(code: 'AP', name: '템플릿'),
      const [],
    );

    verify(
      () => repository.create(any(), steps: any(named: 'steps')),
    ).called(1);
  });

  test('delete 후 목록을 새로고침한다', () async {
    when(() => repository.delete(any())).thenAnswer((_) async {});
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        isActive: any(named: 'isActive'),
      ),
    ).thenAnswer((_) async => createResult([]));

    final result = await controller.delete(1);

    expect(result, isTrue);
    verify(() => repository.delete(1)).called(1);
  });

  test('restore 성공 시 템플릿을 반환하고 버전을 갱신한다', () async {
    when(
      () => repository.restore(any()),
    ).thenAnswer((_) async => sampleTemplate);
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        isActive: any(named: 'isActive'),
      ),
    ).thenAnswer((_) async => createResult([sampleTemplate]));

    final restored = await controller.restore(1);

    expect(restored, isNotNull);
    expect(controller.versionOf(sampleTemplate.id), sampleTemplate.updatedAt);
  });

  test('applyToApproval은 유즈케이스를 호출하고 버전을 갱신한다', () async {
    when(
      () => applyUseCase.call(
        approvalId: any(named: 'approvalId'),
        templateId: any(named: 'templateId'),
      ),
    ).thenAnswer((_) async => createFlow());
    when(
      () => repository.fetchDetail(
        any(),
        includeSteps: any(named: 'includeSteps'),
      ),
    ).thenAnswer((_) async => sampleTemplate);

    final flow = await controller.applyToApproval(
      approvalId: 10,
      templateId: sampleTemplate.id,
    );

    expect(flow, isNotNull);
    expect(controller.isApplyingTemplate, isFalse);
    expect(controller.versionOf(sampleTemplate.id), sampleTemplate.updatedAt);
    verify(
      () => applyUseCase.call(approvalId: 10, templateId: sampleTemplate.id),
    ).called(1);
  });

  test('isTemplateStale은 최신 버전을 판단한다', () async {
    when(
      () => repository.list(
        page: any(named: 'page'),
        pageSize: any(named: 'pageSize'),
        query: any(named: 'query'),
        isActive: any(named: 'isActive'),
      ),
    ).thenAnswer((_) async => createResult([sampleTemplate]));

    await controller.fetch();

    expect(
      controller.isTemplateStale(sampleTemplate.id, sampleTemplate.updatedAt),
      isFalse,
    );
    expect(
      controller.isTemplateStale(
        sampleTemplate.id,
        sampleTemplate.updatedAt!.add(const Duration(minutes: 10)),
      ),
      isTrue,
    );
  });
}
