import React, { useEffect, useRef, useState } from 'react';
import { render, fireEvent, screen } from '@testing-library/react';
import { Form } from '@devesharp/unform-web';
import * as dateFns from 'date-fns';
import { ThemeProvider } from 'styled-components';
import { Datepicker } from './index';
import '@testing-library/jest-dom';
import 'jest-styled-components';
import { Calendar } from '@devesharp/react-utils/lib/Calendar';
import { FormHandles } from '@devesharp/unform-core';

describe('Datepicker', () => {
   it('snapshot - default', () => {
      function Component(): any {
         const [value, setValue] = useState<Date>();

         return (
            <Datepicker
               value={value}
               onValueChange={(v) => {
                  setValue(v);
               }}
            />
         );
      }

      const { container } = render(<Component />);

      expect(container.firstChild).toMatchSnapshot();
   });

   it('snapshot - with title', () => {
      function Component(): any {
         return <Datepicker title="datepicker" value={null} />;
      }

      const { container } = render(<Component />);

      expect(container.firstChild).toMatchSnapshot();
   });

   it('snapshot - with error', () => {
      function Component(): any {
         return <Datepicker error="invalid date" value={null} />;
      }

      const { container } = render(<Component />);

      expect(container.firstChild).toMatchSnapshot();
   });

   it('snapshot - with initial date', () => {
      function Component(): any {
         return <Datepicker error="invalid date" value={new Date(2012, 6, 2, 0, 0)} />;
      }

      const { container } = render(<Component />);

      expect(container.firstChild).toMatchSnapshot();
   });

   it('snapshot - with initial date timepicker', () => {
      function Component(): any {
         return <Datepicker error="invalid date" value={new Date(2012, 6, 2, 0, 0)} withTimePicker />;
      }

      const { container } = render(<Component />);

      expect(container.firstChild).toMatchSnapshot();
   });

   it('snapshot - with initial date timepicker allDay', () => {
      function Component(): any {
         return (
            <Datepicker error="invalid date" value={new Date(2012, 6, 2, 0, 0)} withTimePicker allDay enableAllDay />
         );
      }

      const { container } = render(<Component />);

      expect(container.firstChild).toMatchSnapshot();
   });

   it('snapshot - open', () => {
      function Component(): any {
         const [value, setValue] = useState<Date>(new Date(2012, 6, 2, 0, 0));

         return (
            <Datepicker
               value={value}
               onValueChange={(v) => {
                  setValue(v);
               }}
            />
         );
      }

      const { container } = render(<Component />);

      // hide
      expect(screen.queryByTestId('datepicker-popup')).toBeFalsy();

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // show
      expect(screen.queryByTestId('datepicker-popup')).toBeTruthy();

      expect(container.firstChild).toMatchSnapshot();
   });

   it('snapshot - with global', () => {
      function Component(): any {
         const [value, setValue] = useState<Date>(new Date(2012, 6, 2, 0, 0));

         return (
            <ThemeProvider
               theme={{
                  datePicker: {
                     defaultColor: 'pink',
                  },
               }}
            >
               <Datepicker
                  value={value}
                  onValueChange={(v) => {
                     setValue(v);
                  }}
               />
            </ThemeProvider>
         );
      }

      const { container } = render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // show
      expect(screen.queryByTestId('datepicker-popup')).toBeTruthy();
      expect(container.firstChild).toMatchSnapshot();
   });

   it('open/close datepicker click outside', () => {
      function Component(): any {
         const [value, setValue] = useState<Date>();

         return (
            <div>
               <Datepicker
                  value={value}
                  onValueChange={(v) => {
                     setValue(v);
                  }}
               />
               <div data-testid="outside" />
            </div>
         );
      }

      const { container } = render(<Component />);

      // hide
      expect(screen.queryByTestId('datepicker-popup')).toBeFalsy();
      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // show
      expect(screen.queryByTestId('datepicker-popup')).toBeTruthy();

      // close with click outside
      fireEvent(
         screen.getByTestId('outside'),
         new MouseEvent('mousedown', {
            bubbles: true,
            cancelable: true,
         }),
      );

      // hide
      expect(screen.queryByTestId('datepicker-popup')).toBeFalsy();
   });

   it('next mounth', () => {
      function Component(): any {
         const [value, setValue] = useState<Date>();

         return (
            <div>
               <Datepicker
                  value={value}
                  onValueChange={(v) => {
                     setValue(v);
                  }}
               />
               <div data-testid="outside" />
            </div>
         );
      }

      render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));
      fireEvent.click(screen.getByTestId('datepicker-next-month'));

      const date = dateFns.addMonths(new Date(), 1);
      // show
      expect(screen.queryByTestId('datepicker-month-name')).toHaveTextContent(
         `${Calendar.monthsName[date.getMonth()]}, ${date.getFullYear()}`,
      );
   });

   it('previous mounth', () => {
      function Component(): any {
         const [value, setValue] = useState<Date>();

         return (
            <div>
               <Datepicker
                  value={value}
                  onValueChange={(v) => {
                     setValue(v);
                  }}
               />
               <div data-testid="outside" />
            </div>
         );
      }

      render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));
      fireEvent.click(screen.getByTestId('datepicker-previous-month'));

      const date = dateFns.subMonths(new Date(), 1);
      // show
      expect(screen.queryByTestId('datepicker-month-name')).toHaveTextContent(
         `${Calendar.monthsName[date.getMonth()]}, ${date.getFullYear()}`,
      );
   });

   it('open/close datepicker with button cancel', () => {
      function Component(): any {
         const [value, setValue] = useState<Date>();

         return (
            <div>
               <Datepicker
                  value={value}
                  onValueChange={(v) => {
                     setValue(v);
                  }}
                  applyOnSelect
               />
               <div data-testid="outside" />
            </div>
         );
      }

      const { container } = render(<Component />);

      // hide
      expect(screen.queryByTestId('datepicker-popup')).toBeFalsy();
      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // show
      expect(screen.queryByTestId('datepicker-popup')).toBeTruthy();

      // close with button close
      fireEvent.click(screen.getByTestId('datepicker-cancel'));

      // hide
      expect(screen.queryByTestId('datepicker-popup')).toBeFalsy();
   });

   it('check is date is correct with initial date datepicker', () => {
      function Component(): any {
         const [value, setValue] = useState<Date>(new Date(2012, 6, 2, 0, 0));

         return (
            <div>
               <Datepicker
                  value={value}
                  onValueChange={(v) => {
                     setValue(v);
                  }}
               />
               <div data-testid="outside" />
            </div>
         );
      }

      const { container } = render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // show
      expect(screen.queryByTestId('20120702')).toBeTruthy();
      expect(screen.queryByTestId('20120702')).toHaveClass('select');
   });

   it('check is date is correct with initial date datepicker and timepicker', () => {
      function Component(): any {
         const [value, setValue] = useState<Date>(new Date(2012, 6, 2, 10, 20));

         return (
            <div>
               <Datepicker
                  value={value}
                  onValueChange={(v) => {
                     setValue(v);
                  }}
                  withTimePicker
               />
               <div data-testid="outside" />
            </div>
         );
      }

      const { container } = render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // show
      expect(screen.queryByTestId('20120702')).toBeTruthy();
      expect(screen.queryByTestId('20120702')).toHaveClass('select');
      expect(screen.queryByTestId('datepicker-input-hour')).toHaveValue('10');
      expect(screen.queryByTestId('datepicker-input-minute')).toHaveValue('20');
   });

   it('check is date is correct with initial date datepicker, timepicker and allDay', () => {
      function Component(): any {
         const [value, setValue] = useState<Date>(new Date(2012, 6, 2, 10, 20));

         return (
            <div>
               <Datepicker
                  value={value}
                  onValueChange={(v) => {
                     setValue(v);
                  }}
                  allDay
                  enableAllDay
                  withTimePicker
               />
               <div data-testid="outside" />
            </div>
         );
      }

      const { container } = render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // show
      expect(screen.queryByTestId('20120702')).toBeTruthy();
      expect(screen.queryByTestId('20120702')).toHaveClass('select');
      expect(screen.queryByTestId('datepicker-all-day')).toBeChecked();
   });

   it('onValueChange with datepicker', () => {
      const onChange = jest.fn();

      function Component(): any {
         const [value, setValue] = useState<Date>(new Date(2012, 6, 2, 0, 0));

         return (
            <div>
               <Datepicker value={value} onValueChange={onChange} />
               <div data-testid="outside" />
            </div>
         );
      }

      render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // change date
      fireEvent.click(screen.getByTestId('20120720'));

      // show
      expect(onChange).toHaveBeenCalled();
      expect(onChange).toHaveBeenCalledWith(new Date(2012, 6, 20, 0, 0), null);
   });

   it('onValueChange with datepicker and timepicker', () => {
      const onChange = jest.fn();

      function Component(): any {
         const [value, setValue] = useState<Date>(new Date(2012, 6, 2, 0, 0));

         return (
            <div>
               <Datepicker value={value} onValueChange={onChange} withTimePicker />
               <div data-testid="outside" />
            </div>
         );
      }

      render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // change date
      fireEvent.click(screen.getByTestId('20120720'));
      fireEvent.change(screen.getByTestId('datepicker-input-hour'), { target: { value: '20' } });
      fireEvent.blur(screen.getByTestId('datepicker-input-hour'));
      fireEvent.change(screen.getByTestId('datepicker-input-minute'), { target: { value: '10' } });
      fireEvent.blur(screen.getByTestId('datepicker-input-minute'));
      fireEvent.click(screen.getByTestId('datepicker-apply'));

      // show
      expect(onChange).toHaveBeenCalled();
      expect(onChange).toHaveBeenCalledWith(new Date(2012, 6, 20, 20, 10), null);
   });

   it('onValueChange & onAllDayChange with datepicker and timepicker', () => {
      const onChange = jest.fn();
      const onAllDayChange = jest.fn();

      function Component(): any {
         const [value, setValue] = useState<Date>(new Date(2012, 6, 2, 0, 0));
         const [allDay, setAllDay] = useState(true);

         return (
            <div>
               <Datepicker
                  value={value}
                  onValueChange={onChange}
                  withTimePicker
                  allDay={allDay}
                  onAllDayChange={onAllDayChange}
                  enableAllDay
               />
               <div data-testid="outside" />
            </div>
         );
      }

      render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // change date
      fireEvent.click(screen.getByTestId('20120720'));
      fireEvent.change(screen.getByTestId('datepicker-input-hour'), { target: { value: '20' } });
      fireEvent.blur(screen.getByTestId('datepicker-input-hour'));
      fireEvent.change(screen.getByTestId('datepicker-input-minute'), { target: { value: '10' } });
      fireEvent.blur(screen.getByTestId('datepicker-input-minute'));
      fireEvent.click(screen.getByTestId('datepicker-all-day'));
      fireEvent.click(screen.getByTestId('datepicker-apply'));

      // call
      expect(onChange).toHaveBeenCalled();
      expect(onChange).toHaveBeenCalledWith(new Date(2012, 6, 20, 20, 10), null);

      // call
      expect(onAllDayChange).toHaveBeenCalled();
      expect(onAllDayChange).toHaveBeenCalledWith(false);
   });

   it('onValueChange with datepicker and timepicker formattedString', () => {
      const onChange = jest.fn();

      function Component(): any {
         const [value, setValue] = useState<Date>(new Date(2012, 6, 2, 0, 0));

         return (
            <div>
               <Datepicker
                  value={value}
                  onValueChange={onChange}
                  withTimePicker
                  formattedString="yyyy-MM-dd 'Ás' HH:mm"
               />
               <div data-testid="outside" />
            </div>
         );
      }

      render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // change date
      fireEvent.click(screen.getByTestId('20120720'));
      fireEvent.change(screen.getByTestId('datepicker-input-hour'), { target: { value: '20' } });
      fireEvent.blur(screen.getByTestId('datepicker-input-hour'));
      fireEvent.change(screen.getByTestId('datepicker-input-minute'), { target: { value: '10' } });
      fireEvent.blur(screen.getByTestId('datepicker-input-minute'));
      fireEvent.click(screen.getByTestId('datepicker-apply'));

      // call
      expect(onChange).toHaveBeenCalled();
      expect(onChange).toHaveBeenCalledWith(new Date(2012, 6, 20, 20, 10), '2012-07-20 Ás 20:10');
   });

   it('unform - snapshot - with initial date', () => {
      function Component(): any {
         return (
            <Form onSubmit={() => {}} initialData={{ datepicker: new Date(2012, 6, 2, 0, 0) }}>
               <Datepicker name="datepicker" />
            </Form>
         );
      }

      const { container } = render(<Component />);

      expect(container.firstChild).toMatchSnapshot();
   });

   it('unform - snapshot - with initial date timepicker', () => {
      function Component(): any {
         return (
            <Form onSubmit={() => {}} initialData={{ datepicker: new Date(2012, 6, 2, 0, 0) }}>
               <Datepicker name="datepicker" withTimePicker />
            </Form>
         );
      }

      const { container } = render(<Component />);

      expect(container.firstChild).toMatchSnapshot();
   });

   it('unform - snapshot - with initial date timepicker allDay', () => {
      function Component(): any {
         return (
            <Form onSubmit={() => {}} initialData={{ datepicker: new Date(2012, 6, 2, 0, 0), datepicker_allday: true }}>
               <Datepicker name="datepicker" nameAllDay="datepicker_allday" withTimePicker allDay enableAllDay />
            </Form>
         );
      }

      const { container } = render(<Component />);

      expect(container.firstChild).toMatchSnapshot();
   });

   it('unform - snapshot - with initial string', () => {
      function Component(): any {
         return (
            <Form onSubmit={() => {}} initialData={{ datepicker: '2012-07-20 Ás 20:10' }}>
               <Datepicker name="datepicker" formattedString="yyyy-MM-dd 'Ás' HH:mm" />
            </Form>
         );
      }

      const { container } = render(<Component />);

      expect(container.firstChild).toMatchSnapshot();
   });

   it('unform - snapshot - with initial string timepicker', () => {
      function Component(): any {
         return (
            <Form onSubmit={() => {}} initialData={{ datepicker: '2012-07-20 Ás 20:10' }}>
               <Datepicker name="datepicker" withTimePicker formattedString="yyyy-MM-dd 'Ás' HH:mm" />
            </Form>
         );
      }

      const { container } = render(<Component />);

      expect(container.firstChild).toMatchSnapshot();
   });

   it('unform - check is date is correct with initial date datepicker', () => {
      function Component(): any {
         return (
            <Form onSubmit={(d) => {}} initialData={{ datepicker: new Date(2012, 6, 2, 10, 20) }}>
               <Datepicker name="datepicker" />
            </Form>
         );
      }

      const { container } = render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // show
      expect(screen.queryByTestId('20120702')).toBeTruthy();
      expect(screen.queryByTestId('20120702')).toHaveClass('select');
   });

   it('unform - check is date is correct with initial date datepicker and timepicker', () => {
      function Component(): any {
         return (
            <Form onSubmit={(d) => {}} initialData={{ datepicker: new Date(2012, 6, 2, 10, 20) }}>
               <Datepicker name="datepicker" withTimePicker />
               <div data-testid="outside" />
            </Form>
         );
      }

      const { container } = render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // show
      expect(screen.queryByTestId('20120702')).toBeTruthy();
      expect(screen.queryByTestId('20120702')).toHaveClass('select');
      expect(screen.queryByTestId('datepicker-input-hour')).toHaveValue('10');
      expect(screen.queryByTestId('datepicker-input-minute')).toHaveValue('20');
   });

   it('unform - check is date is correct with initial date datepicker, timepicker and allDay', () => {
      function Component(): any {
         const [value, setValue] = useState<Date>(new Date(2012, 6, 2, 10, 20));

         return (
            <Form
               onSubmit={(d) => {}}
               initialData={{ datepicker: new Date(2012, 6, 2, 10, 20), datepickerAllDay: true }}
            >
               <Datepicker name="datepicker" nameAllDay="datepickerAllDay" enableAllDay withTimePicker />
               <div data-testid="outside" />
            </Form>
         );
      }

      const { container } = render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // show
      expect(screen.queryByTestId('20120702')).toBeTruthy();
      expect(screen.queryByTestId('20120702')).toHaveClass('select');
      expect(screen.queryByTestId('datepicker-all-day')).toBeChecked();
   });

   it('unform - onSubmit with datepicker', () => {
      const onChange = jest.fn();

      function Component(): any {
         return (
            <Form onSubmit={(d) => onChange(d)}>
               <Datepicker name="datepicker" />
               <button data-testid="button">Submit</button>
            </Form>
         );
      }

      render(<Component />);

      fireEvent.click(screen.getByTestId('button'));

      // open
      fireEvent.click(screen.getByTestId('open-popup'));
      // change date
      fireEvent.click(screen.getByTestId(dateFns.format(new Date(), 'yyyyMMdd')));

      //
      fireEvent.click(screen.getByTestId('button'));

      expect(onChange).toHaveBeenCalled();
      expect(onChange).toHaveBeenCalledWith({ datepicker: null });
      expect(onChange).toHaveBeenCalledWith({
         datepicker: dateFns.set(new Date(), {
            hours: 0,
            minutes: 0,
            seconds: 0,
            milliseconds: 0,
         }),
      });
   });

   it('unform - onSubmit with timepicker', () => {
      const onChange = jest.fn();

      function Component(): any {
         return (
            <Form onSubmit={(d) => onChange(d)}>
               <Datepicker name="datepicker" withTimePicker />
               <button data-testid="button">Submit</button>
            </Form>
         );
      }

      render(<Component />);

      fireEvent.click(screen.getByTestId('button'));

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // change date
      fireEvent.click(screen.getByTestId(dateFns.format(new Date(), 'yyyyMMdd')));
      fireEvent.change(screen.getByTestId('datepicker-input-hour'), { target: { value: '20' } });
      fireEvent.blur(screen.getByTestId('datepicker-input-hour'));
      fireEvent.change(screen.getByTestId('datepicker-input-minute'), { target: { value: '10' } });
      fireEvent.blur(screen.getByTestId('datepicker-input-minute'));
      fireEvent.click(screen.getByTestId('datepicker-apply'));

      //
      fireEvent.click(screen.getByTestId('button'));

      expect(onChange).toHaveBeenCalled();
      expect(onChange).toHaveBeenCalledWith({ datepicker: null });
      expect(onChange).toHaveBeenCalledWith({
         datepicker: dateFns.set(new Date(), {
            hours: 20,
            minutes: 10,
            seconds: 0,
            milliseconds: 0,
         }),
      });
   });

   it('unform - onSubmit with timepicker & allDay', () => {
      const onChange = jest.fn();

      function Component(): any {
         return (
            <Form onSubmit={(d) => onChange(d)}>
               <Datepicker name="datepicker" nameAllDay="datepicker_all_day" withTimePicker enableAllDay />
               <button data-testid="button">Submit</button>
            </Form>
         );
      }

      render(<Component />);

      fireEvent.click(screen.getByTestId('button'));

      // open
      fireEvent.click(screen.getByTestId('open-popup'));

      // change date
      fireEvent.click(screen.getByTestId(dateFns.format(new Date(), 'yyyyMMdd')));
      fireEvent.change(screen.getByTestId('datepicker-input-hour'), { target: { value: '20' } });
      fireEvent.blur(screen.getByTestId('datepicker-input-hour'));
      fireEvent.change(screen.getByTestId('datepicker-input-minute'), { target: { value: '10' } });
      fireEvent.blur(screen.getByTestId('datepicker-input-minute'));
      fireEvent.click(screen.getByTestId('datepicker-all-day'));
      fireEvent.click(screen.getByTestId('datepicker-apply'));

      //
      fireEvent.click(screen.getByTestId('button'));

      expect(onChange).toHaveBeenCalled();
      expect(onChange).toHaveBeenCalledWith({ datepicker: null, datepicker_all_day: false });
      expect(onChange).toHaveBeenCalledWith({
         datepicker: dateFns.set(new Date(), {
            hours: 20,
            minutes: 10,
            seconds: 0,
            milliseconds: 0,
         }),
         datepicker_all_day: true,
      });
   });

   it('unform - get value on onSubmit - set data', () => {
      const onSubmit = jest.fn();

      function Component(): any {
         const formRef = useRef<FormHandles>();

         function setData(): void {
            formRef.current.setFieldValue('datepicker', new Date(2012, 6, 2, 0, 0));
         }

         return (
            <>
               <Form onSubmit={(v) => onSubmit(v)} ref={formRef}>
                  <Datepicker name="datepicker" />
                  <button data-testid="setData" title="setData" onClick={setData} />
                  <button
                     data-testid="submit"
                     title="submit"
                     onClick={() => {
                        formRef.current.submitForm();
                     }}
                  />
               </Form>
            </>
         );
      }

      render(<Component />);

      fireEvent.click(screen.getByTestId('setData'));
      fireEvent.click(screen.getByTestId('submit'));

      expect(screen.getByTestId('date-string')).toHaveTextContent('2 Jul 2012');
      expect(onSubmit).toHaveBeenCalled();
      expect(onSubmit).toHaveBeenCalledWith({
         datepicker: new Date(2012, 6, 2, 0, 0),
      });
   });

   it('unform - snapshot - with error', () => {
      function Component(): any {
         const formRef = useRef<FormHandles>();

         useEffect(() => {
            formRef.current.setFieldError('datepicker', 'Field is required');
         }, []);

         return (
            <Form onSubmit={() => {}} initialData={{ checkbox: { id: 2 } }} ref={formRef}>
               <Datepicker name="datepicker" />
            </Form>
         );
      }

      const { container } = render(<Component />);

      expect(screen.getByTestId('input-error')).not.toBeUndefined();

      expect(container.firstChild).toMatchSnapshot();
   });

   it('unform - get value on onSubmit - clear form', () => {
      const onSubmit = jest.fn();

      function Component(): any {
         const formRef = useRef<FormHandles>();

         function clearForm(): void {
            formRef.current.reset();
         }

         return (
            <>
               <Form
                  onSubmit={(v) => onSubmit(v)}
                  initialData={{ datepicker: new Date(), datepickerAllDay: true }}
                  ref={formRef}
               >
                  <Datepicker name="datepicker" nameAllDay="datepickerAllDay" allDay withTimePicker />
                  <button
                     data-testid="submit"
                     title="submit"
                     onClick={() => {
                        formRef.current.submitForm();
                     }}
                  />
                  <button data-testid="clear" title="clear" onClick={clearForm} />
               </Form>
            </>
         );
      }

      render(<Component />);

      fireEvent.click(screen.getByTestId('clear'));
      fireEvent.click(screen.getByTestId('submit'));

      expect(onSubmit).toHaveBeenCalled();
      expect(onSubmit).toHaveBeenCalledWith({
         datepicker: null,
         datepickerAllDay: false,
      });
   });

   it('as popup', () => {
      function Component(): any {
         const [value, setValue] = useState<Date>();

         return (
            <>
               <Datepicker
                  value={value}
                  onValueChange={(v) => {
                     setValue(v);
                  }}
                  asPopup
               >
                  <div data-testid="string">Custom datepicker: {value && dateFns.format(value, 'd MMM yyyy')}</div>
               </Datepicker>
            </>
         );
      }

      render(<Component />);

      // open
      fireEvent.click(screen.getByTestId('open-popup'));
      // change date
      fireEvent.click(screen.getByTestId(dateFns.format(new Date(), 'yyyyMMdd')));

      expect(screen.queryByTestId('string')).toHaveTextContent(dateFns.format(new Date(), 'd MMM yyyy'));
   });
});
