import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:formz/formz.dart';
import 'package:mocktail/mocktail.dart';
import 'package:social_app/core/form_inputs/form_inputs.dart';
import 'package:social_app/features/auth/data/auth_repository.dart';
import 'package:social_app/features/auth/data/models/auth_response.dart';
import 'package:social_app/features/auth/data/models/signup_request.dart';
import 'package:social_app/features/auth/presentation/cubits/register_cubit.dart';
import 'package:social_app/core/api/api_exception.dart';

class MockAuthRepository extends Mock implements AuthRepository {}

void main() {
  late RegisterCubit cubit;
  late MockAuthRepository mockRepository;

  setUp(() {
    mockRepository = MockAuthRepository();
    cubit = RegisterCubit(mockRepository);
    registerFallbackValue(
      SignupStartRequest(username: '', email: '', password: ''),
    );
    registerFallbackValue(SignupResendRequest(email: ''));
  });

  tearDown(() {
    cubit.close();
  });

  group('RegisterCubit', () {
    test('initial state has pure status', () {
      expect(cubit.state.status, FormzSubmissionStatus.initial);
    });

    blocTest<RegisterCubit, RegisterState>(
      'usernameChanged updates username',
      build: () => cubit,
      act: (c) => c.usernameChanged('testuser'),
      expect: () => [isA<RegisterState>()],
    );

    blocTest<RegisterCubit, RegisterState>(
      'emailChanged updates email',
      build: () => cubit,
      act: (c) => c.emailChanged('test@example.com'),
      expect: () => [isA<RegisterState>()],
    );

    blocTest<RegisterCubit, RegisterState>(
      'passwordChanged updates password',
      build: () => cubit,
      act: (c) => c.passwordChanged('password123'),
      expect: () => [isA<RegisterState>()],
    );
  });

  group('sendCodeSilently', () {
    blocTest<RegisterCubit, RegisterState>(
      'sets isSending to true then false on success',
      build: () => cubit,
      seed: () => RegisterState(
        username: const Username.dirty('testuser'),
        email: const Email.dirty('test@example.com'),
        password: const Password.dirty('password123'),
      ),
      setUp: () {
        when(() => mockRepository.createVerification(any())).thenAnswer(
          (_) async => VerificationCreateResponse(email: 'test@example.com'),
        );
      },
      act: (c) => c.sendCodeSilently(),
      expect: () => [
        predicate<RegisterState>((state) => state.isSending == true),
        predicate<RegisterState>(
          (state) =>
              state.isSending == false &&
              state.codeSent == true &&
              state.pendingEmail == 'test@example.com' &&
              state.errorMessage == null,
        ),
      ],
      verify: (_) {
        verify(() => mockRepository.createVerification(any())).called(1);
      },
    );

    blocTest<RegisterCubit, RegisterState>(
      'restores isSending to false and sets errorMessage and failure status on error',
      build: () => cubit,
      seed: () => RegisterState(
        username: const Username.dirty('testuser'),
        email: const Email.dirty('test@example.com'),
        password: const Password.dirty('password123'),
      ),
      setUp: () {
        when(
          () => mockRepository.createVerification(any()),
        ).thenThrow(ServerException('Network error'));
      },
      act: (c) => c.sendCodeSilently(),
      expect: () => [
        predicate<RegisterState>((state) => state.isSending == true),
        predicate<RegisterState>(
          (state) =>
              state.isSending == false &&
              state.errorMessage == 'Network error' &&
              state.status == FormzSubmissionStatus.failure,
        ),
      ],
      verify: (_) {
        verify(() => mockRepository.createVerification(any())).called(1);
      },
    );

    blocTest<RegisterCubit, RegisterState>(
      'does not call createVerification when input is invalid',
      build: () => cubit,
      seed: () => RegisterState(
        username: const Username.dirty(''),
        email: const Email.dirty('invalid'),
        password: const Password.dirty(''),
      ),
      act: (c) => c.sendCodeSilently(),
      expect: () => [],
      verify: (_) {
        verifyNever(() => mockRepository.createVerification(any()));
      },
    );

    blocTest<RegisterCubit, RegisterState>(
      'returns early when isSending is true',
      build: () => cubit,
      seed: () => RegisterState(
        username: const Username.dirty('testuser'),
        email: const Email.dirty('test@example.com'),
        password: const Password.dirty('password123'),
        isSending: true,
      ),
      act: (c) => c.sendCodeSilently(),
      expect: () => [],
      verify: (_) {
        verifyNever(() => mockRepository.createVerification(any()));
      },
    );
  });

  group('resendCode', () {
    blocTest<RegisterCubit, RegisterState>(
      'returns false and sets failure status when pendingEmail is null',
      build: () => cubit,
      seed: () => RegisterState(pendingEmail: null),
      act: (c) => c.resendCode(),
      expect: () => [
        isA<RegisterState>()
            .having((s) => s.status, 'status', FormzSubmissionStatus.failure)
            .having((s) => s.errorMessage, 'errorMessage', '验证码发送失败，请返回上一步重试'),
      ],
      verify: (_) {
        verifyNever(() => mockRepository.resendVerification(any()));
      },
    );

    blocTest<RegisterCubit, RegisterState>(
      'returns true and sets status on success',
      build: () => cubit,
      seed: () => RegisterState(pendingEmail: 'test@example.com'),
      setUp: () {
        when(
          () => mockRepository.resendVerification(any()),
        ).thenAnswer((_) async {});
      },
      act: (c) => c.resendCode(),
      expect: () => [
        isA<RegisterState>().having(
          (s) => s.status,
          'status',
          FormzSubmissionStatus.inProgress,
        ),
        isA<RegisterState>().having(
          (s) => s.status,
          'status',
          FormzSubmissionStatus.success,
        ),
      ],
      verify: (_) {
        verify(() => mockRepository.resendVerification(any())).called(1);
      },
    );

    blocTest<RegisterCubit, RegisterState>(
      'returns false on error',
      build: () => cubit,
      seed: () => RegisterState(pendingEmail: 'test@example.com'),
      setUp: () {
        when(
          () => mockRepository.resendVerification(any()),
        ).thenThrow(ServerException('Network error'));
      },
      act: (c) => c.resendCode(),
      expect: () => [
        isA<RegisterState>().having(
          (s) => s.status,
          'status',
          FormzSubmissionStatus.inProgress,
        ),
        isA<RegisterState>().having(
          (s) => s.status,
          'status',
          FormzSubmissionStatus.failure,
        ),
      ],
    );
  });
}
