import React from 'react';
import { renderHook, act } from '@testing-library/react-hooks';
import { Observable } from 'rxjs';
import { Resolve } from '../../services/resolve.service';
import { useView } from './useView';

const resolveDelay: Resolve<any> = (params: any) =>
   new Observable((obs) => {
      setTimeout(() => {
         obs.next();
         obs.complete();
      }, 10);
   });

const resolveWithData: Resolve<any> = (params: any) =>
   new Observable((obs) => {
      setTimeout(() => {
         obs.next(params);
         obs.complete();
      }, 1);
   });

const resolveErrorDelay: Resolve<any> = (params: any) =>
   new Observable((obs) => {
      setTimeout(() => {
         obs.error('Error on load');
         obs.complete();
      }, 10);
   });

const resolveError: Resolve<any> = (params: any) =>
   new Observable((obs) => {
      obs.error('Error on load');
      obs.complete();
   });

test('useView verificar status info com sucesso nos resolves', async () => {
   const { result, waitForNextUpdate } = renderHook(() =>
      useView({
         resolves: {
            resolve: resolveDelay,
         },
      }),
   );

   // Inicial
   expect(result.current.starting).toBe(true);
   expect(result.current.errorLoadData).toBe(false);
   expect(result.current.criticalError).toBe(false);

   await waitForNextUpdate();

   // Esperar load resolves
   expect(result.current.starting).toBe(false);
   expect(result.current.errorLoadData).toBe(false);
   expect(result.current.criticalError).toBe(false);
});

test('useView verificar status info com erro nos resolves', async () => {
   const { result, waitForNextUpdate } = renderHook(() =>
      useView({
         resolves: {
            resolve: resolveErrorDelay,
         },
      }),
   );

   // Inicial
   expect(result.current.starting).toBe(true);
   expect(result.current.errorLoadData).toBe(false);
   expect(result.current.criticalError).toBe(false);

   await waitForNextUpdate();

   // Esperar load resolves
   expect(result.current.starting).toBe(false);
   expect(result.current.errorLoadData).toBe(true);
   expect(result.current.criticalError).toBe(false);
});

test('useView firstLoad = false não deve realizar resolves', async (done) => {
   const { result, waitForNextUpdate } = renderHook(() => {
      const b = useView({
         firstLoad: false,
         resolves: {
            data: resolveWithData,
            data1: resolveWithData,
            data2: resolveWithData,
         },
      });

      b.registerOnInit((v) => {
         // Não deve retornar nenhum resolve
         expect(v).toEqual({});
         done();
      });

      return b;
   });
});

test('useView verificar status info SEM resolves', async () => {
   const { result } = renderHook(() =>
      useView({
         resolves: {},
      }),
   );

   // Inicial
   expect(result.current.starting).toBe(false);
   expect(result.current.errorLoadData).toBe(false);
   expect(result.current.criticalError).toBe(false);
});

test('useView usar onInit para resgatar valores inicias', async (done) => {
   const { result, waitForNextUpdate } = renderHook(() =>
      useView({
         resolves: {
            data: resolveWithData,
         },
      }),
   );

   result.current.registerOnInit((v) => {
      expect(v).toEqual({
         data: {},
      });
      done();
   });

   await waitForNextUpdate();
});

test('useView usar onInit para resgatar valores inicias com multiplos resolves', async (done) => {
   const { result, waitForNextUpdate } = renderHook(() =>
      useView({
         resolves: {
            data: resolveWithData,
            data1: resolveWithData,
            data2: resolveWithData,
         },
      }),
   );

   result.current.registerOnInit((v) => {
      expect(v).toEqual({
         data: {},
         data1: {},
         data2: {},
      });
      done();
   });

   await waitForNextUpdate();
});

test('useView usar onInitError quando houver erro no resolve', async (done) => {
   const { result, waitForNextUpdate } = renderHook(() =>
      useView({
         resolves: {
            data: resolveErrorDelay,
         },
      }),
   );

   result.current.registerOnInitError((err) => {
      expect(err).toEqual('Error on load');
      done();
   });

   await waitForNextUpdate();
});

test('useView chamar onInitError quando houver erro em um dos resolves', async (done) => {
   const { result, waitForNextUpdate } = renderHook(() =>
      useView({
         resolves: {
            data: resolveErrorDelay,
            data1: resolveWithData,
         },
      }),
   );

   result.current.registerOnInitError((err) => {
      expect(err).toEqual('Error on load');
      done();
   });

   await waitForNextUpdate();
});

test('useView chamar onInitError quando houver erro em vários resolves', async (done) => {
   const { result, waitForNextUpdate } = renderHook(() =>
      useView({
         resolves: {
            dataError: resolveErrorDelay,
            dataError1: resolveErrorDelay,
            data1: resolveWithData,
            dataError2: resolveErrorDelay,
         },
      }),
   );

   result.current.registerOnInitError((err) => {
      expect(err).toEqual('Error on load');
      done();
   });

   await waitForNextUpdate();
});

test('useView sem getParamsResolve deve definir {} como padrão para resolver', async (done) => {
   const resolveData = (params: any): any =>
      new Observable((obs) => {
         // Deve ser padrão {}
         expect(params).toEqual({});
         obs.next();
         obs.complete();
         done();
      });

   const { result, rerender, waitForNextUpdate } = renderHook((initialValue) => useView(initialValue), {
      initialProps: {
         resolves: {
            data: resolveData,
         },
      },
   });

   rerender({
      resolves: {
         data: resolveWithData,
      },
   });
});

test('useView com getParamsResolve deve definir parametro para resolver', async (done) => {
   const resolveData = (params: any): any =>
      new Observable((obs) => {
         // Deve ser o definido em registerResolveParams
         expect(params).toEqual({
            params: true,
         });
         obs.next();
         obs.complete();
         done();
      });

   const { result, rerender, waitForNextUpdate } = renderHook(
      (initialValue) => {
         const view = useView(initialValue);

         view.registerResolveParams('data', () => ({
            params: true,
         }));

         return view;
      },
      {
         initialProps: {
            resolves: {
               data: resolveData,
            },
         },
      },
   );
});

test('useView com getParamsResolve deve definir parametro para APENAS para resolver definido', async (done) => {
   const resolveData = (params: any): any =>
      new Observable((obs) => {
         // Deve ser padrão {}
         expect(params).toEqual({});
         obs.next();
         obs.complete();
         done();
      });

   const { result, rerender, waitForNextUpdate } = renderHook(
      (initialValue) => {
         const view = useView(initialValue);

         view.registerResolveParams('data', () => ({
            params: true,
         }));

         return view;
      },
      {
         initialProps: {
            resolves: {
               dataOther: resolveData,
            },
         },
      },
   );
});

test('useView chamar reloadPage deve recarregar página', async () => {
   const { result, rerender, waitForNextUpdate } = renderHook((initialValue) => useView(initialValue), {
      initialProps: {
         resolves: {
            data: resolveError,
         },
      },
   });

   // Status com erro
   expect(result.current.starting).toBe(false);
   expect(result.current.errorLoadData).toBe(true);
   expect(result.current.criticalError).toBe(false);

   rerender({
      resolves: {
         data: resolveWithData,
      },
   });

   // Status com erro
   expect(result.current.starting).toBe(false);
   expect(result.current.errorLoadData).toBe(true);
   expect(result.current.criticalError).toBe(false);

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

   // Status reiniciando
   expect(result.current.starting).toBe(true);
   expect(result.current.errorLoadData).toBe(false);
   expect(result.current.criticalError).toBe(false);

   await waitForNextUpdate();

   // Status iniciado sem erro
   expect(result.current.starting).toBe(false);
   expect(result.current.errorLoadData).toBe(false);
   expect(result.current.criticalError).toBe(false);
});

test('useView chamar reloadPage deve chamar onInit', async (done) => {
   const { result, rerender, waitForNextUpdate } = renderHook((initialValue) => useView(initialValue), {
      initialProps: {
         resolves: {
            data: resolveErrorDelay,
         },
      },
   });

   result.current.registerOnInit((v) => {
      expect(v).toEqual({
         data: {},
      });
      done();
   });

   rerender({
      resolves: {
         data: resolveWithData,
      },
   });

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

   // Restar página com valores iniciais
   await waitForNextUpdate();

   // Espera 1s para fazer novamente a request
   await waitForNextUpdate();
});
