import React from 'react';
import { renderHook, act } from '@testing-library/react-hooks';
import { Observable } from 'rxjs';
import * as Yup from 'yup';
import { useViewForm } from './useViewForm';

const resolveGet = (params: any) =>
   new Observable((obs) => {
      setTimeout(() => {
         obs.next({
            id: 1,
            name: 'John',
         });
         obs.complete();
      }, 1);
   });

test('useViewForm - com ID deve resgatar e colocar em resource', async () => {
   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         id: 1,
         resolveGet,
      }),
   );

   await waitForNextUpdate();

   expect(result.current.resource).toEqual({
      id: 1,
      name: 'John',
   });
});

test('useViewForm - sem ID, não deve fazer nada', async () => {
   const { result } = renderHook(() =>
      useViewForm({
         id: null,
         resolveGet,
      }),
   );

   expect(result.current.resource).toEqual({});
});

test('useViewForm - submit deve chamar submitForm de unform', async () => {
   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         id: null,
         resolveGet,
      }),
   );

   result.current.formRef.current = {
      submitForm: jest.fn(),
   } as any;

   act(() => {
      result.current.submit();
   });

   expect(result.current.formRef.current.submitForm).toHaveBeenCalledTimes(1);
});

test('useViewForm - handleFormData como função sincrona', async (done) => {
   const resolveCreate = (params: any) =>
      new Observable((obs) => {
         setTimeout(() => {
            /**
             * Deve mesclar os dois objecto
             */
            expect(params).toEqual({
               age: 14,
               name: 'john',
            });
            done();
            obs.next({});
            obs.complete();
         }, 1);
      });

   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         id: null,
         resolveGet,
         resolveCreate,
         handleFormData: (formData: any) => ({
            ...formData,
            name: 'john',
         }),
      }),
   );

   act(() => {
      result.current.onSubmitForm({ age: 14 });
   });

   await waitForNextUpdate();
   await waitForNextUpdate();
});

test('useViewForm - handleFormData como função assincrona', async (done) => {
   const resolveCreate = (params: any) =>
      new Observable((obs) => {
         setTimeout(() => {
            /**
             * Deve mesclar os dois objecto
             */
            expect(params).toEqual({
               age: 14,
               name: 'john',
            });
            done();
            obs.next({});
            obs.complete();
         }, 1);
      });

   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         id: null,
         resolveGet,
         resolveCreate,
         handleFormData: (formData: any): Promise<any> => {
            return new Promise((resolve) => {
               resolve({
                  ...formData,
                  name: 'john',
               });
            });
         },
      }),
   );

   act(() => {
      result.current.onSubmitForm({ age: 14 });
   });

   await waitForNextUpdate();
   await waitForNextUpdate();
});

test('useViewForm - onValidateFormData = false deve chamar onErrorValidate', async (done) => {
   // Deve chamar onErrorValidate
   const onErrorValidate = (): any => {
      done();
      return jest.fn();
   };

   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         id: null,
         resolveGet,
         onErrorValidate,
         onValidateFormData: (): any => false,
      }),
   );

   act(() => {
      result.current.onSubmitForm({ age: 14 });
   });
});

test('useViewForm - resolveCreate deve ser chamado ao salvar form se id = null', async () => {
   const mockCall = jest.fn();

   const resolveCreate: any = (params: any) =>
      new Observable((obs) => {
         mockCall();
         obs.next({});
         obs.complete();
      });

   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         id: null,
         resolveGet,
         resolveCreate,
      }),
   );

   act(() => {
      result.current.onSubmitForm({ age: 14 });
   });

   await waitForNextUpdate();

   // 1
   expect(mockCall).toHaveBeenCalled();
});

test('useViewForm - chamar onSuccess em resolveCreate', async () => {
   const onSuccess = jest.fn();

   const resolveCreate: any = (params: any) =>
      new Observable((obs) => {
         obs.next({});
         obs.complete();
      });

   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         id: null,
         resolveGet,
         resolveCreate,
         onSuccess,
      }),
   );

   act(() => {
      result.current.onSubmitForm({ age: 14 });
   });

   await waitForNextUpdate();

   // 1
   expect(onSuccess).toHaveBeenCalled();
   expect(onSuccess).toHaveBeenCalledWith(true, {});
});

test('useViewForm - chamar onFailed em resolveCreate', async () => {
   const onFailed = jest.fn();

   const resolveCreate: any = (params: any) =>
      new Observable((obs) => {
         obs.error({});
         obs.complete();
      });

   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         id: null,
         resolveGet,
         resolveCreate,
         onFailed,
      }),
   );

   act(() => {
      result.current.onSubmitForm({ age: 14 });
   });

   await waitForNextUpdate();

   // 1
   expect(onFailed).toHaveBeenCalled();
   expect(onFailed).toHaveBeenCalledWith(true, {});
});

test('useViewForm - resolveUpdate deve ser chamado ao salvar form se id = null', async () => {
   const mockCall = jest.fn();

   const resolveUpdate: any = (params: any) =>
      new Observable((obs) => {
         mockCall();
         obs.next({});
         obs.complete();
      });

   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         id: 1,
         resolveGet,
         resolveUpdate,
      }),
   );

   await waitForNextUpdate();

   act(() => {
      result.current.onSubmitForm({ age: 14 });
   });

   await waitForNextUpdate();

   // 1
   expect(mockCall).toHaveBeenCalled();
});

test('useViewForm - chamar onSuccess em resolveUpdate', async () => {
   const onSuccess = jest.fn();

   const resolveUpdate: any = (params: any) =>
      new Observable((obs) => {
         obs.next({});
         obs.complete();
      });

   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         id: 1,
         resolveGet,
         resolveUpdate,
         onSuccess,
      }),
   );

   await waitForNextUpdate();

   act(() => {
      result.current.onSubmitForm({ age: 14 });
   });

   await waitForNextUpdate();

   // 1
   expect(onSuccess).toHaveBeenCalled();
   expect(onSuccess).toHaveBeenCalledWith(false, {});
});

test('useViewForm - chamar onFailed em resolveUpdate', async () => {
   const onFailed = jest.fn();

   const resolveUpdate: any = (params: any) =>
      new Observable((obs) => {
         obs.error({});
         obs.complete();
      });

   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         id: 1,
         resolveGet,
         resolveUpdate,
         onFailed,
      }),
   );

   await waitForNextUpdate();

   act(() => {
      result.current.onSubmitForm({ age: 14 });
   });

   await waitForNextUpdate();

   // 1
   expect(onFailed).toHaveBeenCalled();
   expect(onFailed).toHaveBeenCalledWith(false, {});
});

test('useViewForm - validar dados do formulário com yupValidateSchema', async (done) => {
   const onErrorValidate = jest.fn();

   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         id: 1,
         resolveGet,
         onErrorValidate,
         yupValidateSchema: Yup.object().shape({
            name: Yup.string().required('Obrigatório'),
            age: Yup.string().required('Estado é obrigatório'),
         }),
      }),
   );

   await waitForNextUpdate();

   result.current.formRef.current = {
      submitForm: jest.fn(),
      setErrors: jest.fn(),
   } as any;

   act(() => {
      result.current.onSubmitForm({ age: 14 });
   });

   // Pequeno delay para executar função
   setTimeout(() => {
      expect(result.current.formRef.current.setErrors).toHaveBeenCalledWith({});
      expect(result.current.formRef.current.setErrors).toHaveBeenCalledWith({ name: 'Obrigatório' });
      expect(result.current.formRef.current.setErrors).toHaveBeenCalledTimes(2);
      expect(onErrorValidate).toHaveBeenCalled();
      done();
   }, 100);
});

test('useViewForm - resolveUpdate deve ativar saving', async () => {
   const resolveUpdate: any = (params: any) =>
      new Observable((obs) => {
         setTimeout(() => {
            obs.error({});
            obs.complete();
         }, 1);
      });

   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         id: 1,
         resolveGet,
         resolveUpdate,
      }),
   );

   await waitForNextUpdate();

   act(() => {
      result.current.onSubmitForm({ age: 14 });
   });
   await waitForNextUpdate();

   // Salvando
   expect(result.current.saving).toBe(true);
   await waitForNextUpdate();

   // Salvo
   expect(result.current.saving).toBe(false);
});

test('useViewForm - resolveCreate deve ativar saving', async () => {
   const resolveCreate: any = (params: any) =>
      new Observable((obs) => {
         setTimeout(() => {
            obs.error({});
            obs.complete();
         }, 1);
      });

   const { result, waitForNextUpdate } = renderHook(() =>
      useViewForm({
         resolveGet,
         resolveCreate,
      }),
   );

   act(() => {
      result.current.onSubmitForm({ age: 14 });
   });
   await waitForNextUpdate();

   // Salvando
   expect(result.current.saving).toBe(true);
   await waitForNextUpdate();

   // Salvo
   expect(result.current.saving).toBe(false);
});
