import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Route, Routes } from 'react-router-dom-v5-compat';
import { useEffectOnce } from 'react-use';
import { type Props as AutoSizerProps } from 'react-virtualized-auto-sizer';
import { render } from 'test/test-utils';

import { selectors as e2eSelectors } from '@grafana/e2e-selectors';
import {
  type Dashboard,
  DashboardCursorSync,
  type FieldConfigSource,
  type Panel,
  ThresholdsMode,
} from '@grafana/schema';
import { getRouteComponentProps } from 'app/core/navigation/mocks/routeProps';
import { DashboardInitPhase, type DashboardMeta, DashboardRoutes } from 'app/types/dashboard';
import { type StoreState } from 'app/types/store';

import { configureStore } from '../../../store/configureStore';
import { type Props as LazyLoaderProps } from '../dashgrid/LazyLoader';
import { DashboardModel } from '../state/DashboardModel';
import { initDashboard } from '../state/initDashboard';

import PublicDashboardPage, { type Props } from './PublicDashboardPage';

jest.mock('app/features/dashboard/dashgrid/LazyLoader', () => {
  const LazyLoader = ({ children, onLoad }: Pick<LazyLoaderProps, 'children' | 'onLoad'>) => {
    useEffectOnce(() => {
      onLoad?.();
    });
    return <>{typeof children === 'function' ? children({ isInView: true }) : children}</>;
  };
  return { LazyLoader };
});

jest.mock('react-virtualized-auto-sizer', () => {
  // The size of the children need to be small enough to be outside the view.
  // So it does not trigger the query to be run by the PanelQueryRunner.
  return ({ children }: AutoSizerProps) =>
    children({
      height: 1,
      scaledHeight: 1,
      scaledWidth: 1,
      width: 1,
    });
});

jest.mock('app/features/dashboard/state/initDashboard', () => ({
  ...jest.requireActual('app/features/dashboard/state/initDashboard'),
  initDashboard: jest.fn(),
}));

jest.mock('app/types/store', () => ({
  ...jest.requireActual('app/types/store'),
  useDispatch: () => jest.fn(),
}));

const setup = (propOverrides?: Partial<Props>, initialState?: Partial<StoreState>) => {
  const store = configureStore(initialState);
  const props: Props = {
    ...getRouteComponentProps({
      route: {
        routeName: DashboardRoutes.Public,
        path: '/public-dashboards/:accessToken',
        component: () => null,
      },
    }),
  };

  Object.assign(props, propOverrides);

  render(
    <Routes>
      <Route path="/public-dashboards/:accessToken" element={<PublicDashboardPage {...props} />} />
    </Routes>,
    { store, historyOptions: { initialEntries: [`/public-dashboards/an-access-token`] } }
  );

  const wrappedRerender = (newProps: Partial<Props>) => {
    Object.assign(props, newProps);
    return render(
      <Routes>
        <Route path="/public-dashboards/:accessToken" element={<PublicDashboardPage {...props} />} />
      </Routes>,
      { store, historyOptions: { initialEntries: [`/public-dashboards/an-access-token`] } }
    );
  };

  return { rerender: wrappedRerender };
};

const selectors = e2eSelectors.components;
const publicDashboardSelector = e2eSelectors.pages.PublicDashboard;

const getTestDashboard = (overrides?: Partial<Dashboard>, metaOverrides?: Partial<DashboardMeta>): DashboardModel => {
  const data: Dashboard = Object.assign(
    {
      title: 'My dashboard',
      revision: 1,
      editable: false,
      graphTooltip: DashboardCursorSync.Off,
      schemaVersion: 1,
      timepicker: { hidden: true },
      timezone: '',
      panels: [
        {
          id: 1,
          type: 'timeseries',
          title: 'My panel title',
          gridPos: { x: 0, y: 0, w: 1, h: 1 },
        },
      ],
    },
    overrides
  );

  return new DashboardModel(data, metaOverrides);
};

const dashboardBase = {
  getModel: getTestDashboard,
  initError: null,
  initPhase: DashboardInitPhase.Completed,
  permissions: [],
};

describe('PublicDashboardPage', () => {
  beforeEach(() => {
    jest.clearAllMocks();
  });

  it('Should call initDashboard on mount', () => {
    setup();
    expect(initDashboard).toBeCalledWith({
      fixUrl: false,
      accessToken: 'an-access-token',
      routeName: 'public-dashboard',
      keybindingSrv: expect.anything(),
    });
  });

  describe('Given a simple public dashboard', () => {
    const newState = {
      dashboard: dashboardBase,
    };

    it('Should render panels', async () => {
      setup(undefined, newState);
      expect(await screen.findByText('My panel title')).toBeInTheDocument();
    });

    it('Should update title', async () => {
      setup(undefined, newState);
      await waitFor(() => {
        expect(document.title).toBe('My dashboard - Grafana');
      });
    });

    it('Should not render neither time range nor refresh picker buttons', async () => {
      setup(undefined, newState);
      await waitFor(() => {
        expect(screen.queryByTestId(selectors.TimePicker.openButton)).not.toBeInTheDocument();
        expect(screen.queryByTestId(selectors.RefreshPicker.runButtonV2)).not.toBeInTheDocument();
        expect(screen.queryByTestId(selectors.RefreshPicker.intervalButtonV2)).not.toBeInTheDocument();
      });
    });

    it('Should not render paused or deleted screen', async () => {
      setup(undefined, newState);
      await waitFor(() => {
        expect(screen.queryByTestId(publicDashboardSelector.NotAvailable.container)).not.toBeInTheDocument();
      });
    });

    it('Should render panel with hover widget but without drag icon when panel title is undefined', async () => {
      const fieldConfig: FieldConfigSource = {
        defaults: {
          thresholds: {
            mode: ThresholdsMode.Absolute,
            steps: [
              {
                color: 'green',
                value: 1,
              },
              {
                color: 'red',
                value: 80,
              },
            ],
          },
        },
        overrides: [],
      };
      const panels: Panel[] = [
        {
          id: 1,
          fieldConfig,
          gridPos: {
            h: 8,
            w: 12,
            x: 0,
            y: 0,
          },
          options: {},
          title: undefined,
          type: 'timeseries',
          repeatDirection: 'h',
          transformations: [],
          transparent: false,
          description: 'hello',
        },
      ];

      const newState = {
        dashboard: {
          ...dashboardBase,
          getModel: () => getTestDashboard({ panels }),
        },
      };
      setup(undefined, newState);

      await waitFor(() => {
        expect(screen.getByTestId(selectors.Panels.Panel.HoverWidget.container)).toBeInTheDocument();
      });
      await userEvent.hover(screen.getByTestId(selectors.Panels.Panel.HoverWidget.container));
      expect(screen.queryByTestId(selectors.Panels.Panel.HoverWidget.dragIcon)).not.toBeInTheDocument();
    });

    it('Should render panel without hover widget when panel title is not undefined', async () => {
      setup(undefined, newState);
      await waitFor(() => {
        expect(screen.queryByTestId(selectors.Panels.Panel.HoverWidget.container)).not.toBeInTheDocument();
      });
    });
  });

  describe('When public dashboard changes', () => {
    it('Should init again', async () => {
      const { rerender } = setup();
      rerender({});
      await waitFor(() => {
        expect(initDashboard).toHaveBeenCalledTimes(2);
      });
    });
  });

  describe('Given a public dashboard with time range enabled', () => {
    it('Should render time range and refresh picker buttons', async () => {
      setup(undefined, {
        dashboard: {
          ...dashboardBase,
          getModel: () =>
            getTestDashboard({
              timepicker: { hidden: false, refresh_intervals: [] },
            }),
        },
      });
      expect(await screen.findByTestId(selectors.TimePicker.openButton)).toBeInTheDocument();
      expect(screen.getByTestId(selectors.RefreshPicker.runButtonV2)).toBeInTheDocument();
      expect(screen.getByTestId(selectors.RefreshPicker.intervalButtonV2)).toBeInTheDocument();
    });
  });

  describe('Given paused public dashboard', () => {
    it('Should render public dashboard paused screen', async () => {
      setup(undefined, {
        dashboard: {
          ...dashboardBase,
          initError: {
            message: 'Failed to fetch dashboard',
            error: {
              status: 403,
              statusText: 'Forbidden',
              data: {
                statusCode: 403,
                messageId: 'publicdashboards.notEnabled',
                message: 'Dashboard paused',
              },
              config: {
                method: 'GET',
                url: 'api/public/dashboards/4615c835a4e441f09c94fb1b073e6d2e',
                retry: 0,
                headers: {
                  'X-Grafana-Org-Id': 1,
                  'X-Grafana-Device-Id': 'da48fad0e58ba327fd7d1e6bd17e9c63',
                },
                hideFromInspector: true,
              },
            },
          },
          getModel: () => getTestDashboard(undefined, { publicDashboardEnabled: false }),
        },
      });

      await waitFor(() => {
        expect(screen.queryByTestId(publicDashboardSelector.page)).not.toBeInTheDocument();
      });
      expect(screen.getByTestId(publicDashboardSelector.NotAvailable.title)).toBeInTheDocument();
      expect(screen.getByTestId(publicDashboardSelector.NotAvailable.pausedDescription)).toBeInTheDocument();
    });
  });

  describe('Given deleted public dashboard', () => {
    it('Should render public dashboard deleted screen', async () => {
      setup(undefined, {
        dashboard: {
          ...dashboardBase,
          initError: {
            message: 'Failed to fetch dashboard',
            error: {
              status: 404,
              statusText: 'Not Found',
              data: {
                statusCode: 404,
                messageId: 'publicdashboards.notFound',
                message: 'Dashboard not found',
              },
              config: {
                method: 'GET',
                url: 'api/public/dashboards/ce159fe139fc4d238a7d9c3ae33fb82b',
                retry: 0,
                hideFromInspector: true,
                headers: {
                  'X-Grafana-Device-Id': 'da48fad0e58ba327fd7d1e6bd17e9c63',
                },
              },
            },
          },
          getModel: () => getTestDashboard(undefined, {}),
        },
      });

      await waitFor(() => {
        expect(screen.queryByTestId(publicDashboardSelector.page)).not.toBeInTheDocument();
        expect(screen.queryByTestId(publicDashboardSelector.NotAvailable.pausedDescription)).not.toBeInTheDocument();
      });
      expect(screen.getByTestId(publicDashboardSelector.NotAvailable.title)).toBeInTheDocument();
    });
  });
});
