import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';

import { BootData, DataQuery } from '@grafana/data/src';
import { selectors as e2eSelectors } from '@grafana/e2e-selectors/src';
import { reportInteraction, setEchoSrv } from '@grafana/runtime';
import { Panel } from '@grafana/schema';
import config from 'app/core/config';
import { backendSrv } from 'app/core/services/backend_srv';
import { contextSrv } from 'app/core/services/context_srv';
import { Echo } from 'app/core/services/echo/Echo';
import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures';
import { DashboardInteractions } from 'app/features/dashboard-scene/utils/interactions';

import { shareDashboardType } from '../utils';

import * as sharePublicDashboardUtils from './SharePublicDashboardUtils';
import {
  getExistentPublicDashboardResponse,
  mockDashboard,
  pubdashResponse,
  renderSharePublicDashboard,
} from './utilsTest';

const server = setupServer();

jest.mock('@grafana/runtime', () => ({
  ...jest.requireActual('@grafana/runtime'),
  getBackendSrv: () => backendSrv,
  reportInteraction: jest.fn(),
}));

jest.mock('app/features/dashboard-scene/utils/interactions', () => ({
  DashboardInteractions: {
    ...jest.requireActual('app/features/dashboard-scene/utils/interactions').DashboardInteractions,
    sharingTabChanged: jest.fn(),
  },
}));

const selectors = e2eSelectors.pages.ShareDashboardModal.PublicDashboard;

let originalBootData: BootData;

beforeAll(() => {
  setEchoSrv(new Echo());
  originalBootData = config.bootData;
  config.appUrl = 'http://dashboards.grafana.com/';
  config.bootData = {
    user: {
      orgId: 1,
    },
    navTree: [
      {
        text: 'Section name',
        id: 'section',
        url: 'section',
        children: [
          { text: 'Child1', id: 'child1', url: 'section/child1' },
          { text: 'Child2', id: 'child2', url: 'section/child2' },
        ],
      },
    ],
  } as BootData;

  server.listen({ onUnhandledRequest: 'bypass' });
});

beforeEach(() => {
  config.publicDashboardsEnabled = true;

  jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(true);
  jest.spyOn(contextSrv, 'hasRole').mockReturnValue(true);
});

afterAll(() => {
  config.bootData = originalBootData;
  server.close();
});

afterEach(() => {
  jest.restoreAllMocks();
  server.resetHandlers();
});

const getNonExistentPublicDashboardResponse = () =>
  http.get('/api/dashboards/uid/:dashboardUid/public-dashboards', () => {
    return HttpResponse.json(
      {
        message: 'Public dashboard not found',
        messageId: 'publicdashboards.notFound',
        statusCode: 404,
        traceID: '',
      },
      {
        status: 404,
      }
    );
  });
const getErrorPublicDashboardResponse = () =>
  http.get('/api/dashboards/uid/:dashboardUid/public-dashboards', () => {
    return HttpResponse.json(
      {},
      {
        status: 500,
      }
    );
  });

const alertTests = () => {
  it('when user has no write permissions, warning is shown', async () => {
    jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);

    await renderSharePublicDashboard();
    expect(screen.queryByTestId(selectors.NoUpsertPermissionsWarningAlert)).toBeInTheDocument();
  });
  it('when dashboard has template variables, warning is shown', async () => {
    jest.spyOn(sharePublicDashboardUtils, 'dashboardHasTemplateVariables').mockReturnValue(true);

    await renderSharePublicDashboard();
    expect(screen.queryByTestId(selectors.TemplateVariablesWarningAlert)).toBeInTheDocument();
  });
  it('when dashboard has unsupported datasources, warning is shown', async () => {
    const panelModel = {
      targets: [
        {
          datasource: { type: 'notSupportedDatasource', uid: 'abc123' },
        } as DataQuery,
      ] as DataQuery[],
    } as unknown as Panel;
    const dashboard = createDashboardModelFixture({
      id: 1,
      panels: [panelModel],
    });

    await renderSharePublicDashboard({ dashboard });
    expect(await screen.findByTestId(selectors.UnsupportedDataSourcesWarningAlert)).toBeInTheDocument();
  });
};

describe('SharePublic', () => {
  beforeEach(() => {
    server.use(getExistentPublicDashboardResponse());
  });
  it('does not render share panel when public dashboards feature is disabled using config setting', async () => {
    config.publicDashboardsEnabled = false;
    await renderSharePublicDashboard(undefined, false);

    expect(screen.getByRole('tablist')).toHaveTextContent('Link');
    expect(screen.getByRole('tablist')).not.toHaveTextContent('Public dashboard');
  });
  it('renders default relative time in settings summary when they are closed', async () => {
    expect(mockDashboard.time).toEqual({ from: 'now-6h', to: 'now' });

    //@ts-ignore
    mockDashboard.originalTime = { from: 'now-6h', to: 'now' };

    await renderSharePublicDashboard();
    await waitFor(() => screen.getByText('Time range ='));

    expect(screen.getByText('Last 6 hours')).toBeInTheDocument();
  });
  it('renders default relative time in settings when they are open', async () => {
    expect(mockDashboard.time).toEqual({ from: 'now-6h', to: 'now' });

    //@ts-ignore
    mockDashboard.originalTime = { from: 'now-6h', to: 'now' };

    await renderSharePublicDashboard();
    await userEvent.click(screen.getByText('Settings'));

    expect(screen.queryAllByText('Last 6 hours')).toHaveLength(2);
  });
  it('when modal is opened, then checkboxes are enabled but create button is disabled', async () => {
    server.use(getNonExistentPublicDashboardResponse());
    await renderSharePublicDashboard();

    expect(screen.getByTestId(selectors.WillBePublicCheckbox)).toBeEnabled();
    expect(screen.getByTestId(selectors.LimitedDSCheckbox)).toBeEnabled();
    expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeEnabled();
    expect(screen.getByTestId(selectors.CreateButton)).toBeDisabled();
    expect(screen.queryByTestId(selectors.DeleteButton)).not.toBeInTheDocument();
  });
  it('when fetch errors happen, then all inputs remain disabled', async () => {
    server.use(getErrorPublicDashboardResponse());

    await renderSharePublicDashboard();

    expect(screen.getByTestId(selectors.WillBePublicCheckbox)).toBeDisabled();
    expect(screen.getByTestId(selectors.LimitedDSCheckbox)).toBeDisabled();
    expect(screen.getByTestId(selectors.CostIncreaseCheckbox)).toBeDisabled();
    expect(screen.getByTestId(selectors.CreateButton)).toBeDisabled();
    expect(screen.queryByTestId(selectors.DeleteButton)).not.toBeInTheDocument();
  });
});

describe('SharePublic - New config setup', () => {
  beforeEach(() => {
    server.use(getNonExistentPublicDashboardResponse());
  });

  it('renders when public dashboards feature is enabled', async () => {
    await renderSharePublicDashboard();

    await screen.findByText('Welcome to public dashboards!');
    expect(screen.getByText('Generate public URL')).toBeInTheDocument();

    expect(screen.queryByTestId(selectors.WillBePublicCheckbox)).toBeInTheDocument();
    expect(screen.queryByTestId(selectors.LimitedDSCheckbox)).toBeInTheDocument();
    expect(screen.queryByTestId(selectors.CostIncreaseCheckbox)).toBeInTheDocument();
    expect(screen.queryByTestId(selectors.CreateButton)).toBeInTheDocument();

    expect(screen.queryByTestId(selectors.DeleteButton)).not.toBeInTheDocument();
  });
  it('when modal is opened, then create button is disabled', async () => {
    await renderSharePublicDashboard();
    expect(screen.getByTestId(selectors.CreateButton)).toBeDisabled();
  });
  it('when checkboxes are filled, then create button is enabled', async () => {
    await renderSharePublicDashboard();

    await userEvent.click(screen.getByTestId(selectors.WillBePublicCheckbox));
    await userEvent.click(screen.getByTestId(selectors.LimitedDSCheckbox));
    await userEvent.click(screen.getByTestId(selectors.CostIncreaseCheckbox));

    expect(screen.getByTestId(selectors.CreateButton)).toBeEnabled();
  });
  alertTests();
});

describe('SharePublic - Already persisted', () => {
  beforeEach(() => {
    server.use(getExistentPublicDashboardResponse());
  });

  it('when modal is opened, then delete button is enabled', async () => {
    await renderSharePublicDashboard();
    await waitFor(() => {
      expect(screen.getByTestId(selectors.DeleteButton)).toBeEnabled();
    });
  });
  it('when fetch is done, then inputs are checked and delete button is enabled', async () => {
    await renderSharePublicDashboard();
    await userEvent.click(screen.getByText('Settings'));

    await waitFor(() => {
      expect(screen.getByTestId(selectors.EnableTimeRangeSwitch)).toBeEnabled();
    });
    expect(screen.getByTestId(selectors.EnableTimeRangeSwitch)).toBeChecked();

    expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeEnabled();
    expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeChecked();

    expect(screen.getByTestId(selectors.PauseSwitch)).toBeEnabled();
    expect(screen.getByTestId(selectors.PauseSwitch)).not.toBeChecked();

    expect(screen.getByTestId(selectors.DeleteButton)).toBeEnabled();
  });
  it('inputs and delete button are disabled because of lack of permissions', async () => {
    jest.spyOn(contextSrv, 'hasPermission').mockReturnValue(false);
    await renderSharePublicDashboard();
    await userEvent.click(screen.getByText('Settings'));

    expect(await screen.findByTestId(selectors.EnableTimeRangeSwitch)).toBeDisabled();
    expect(screen.getByTestId(selectors.EnableTimeRangeSwitch)).toBeChecked();

    expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeDisabled();
    expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeChecked();

    expect(screen.getByTestId(selectors.PauseSwitch)).toBeDisabled();
    expect(screen.getByTestId(selectors.PauseSwitch)).not.toBeChecked();

    expect(screen.queryByTestId(selectors.DeleteButton)).toBeDisabled();
  });
  it('when modal is opened, then time range switch is enabled and not checked when its not checked in the db', async () => {
    server.use(
      http.get('/api/dashboards/uid/:dashboardUid/public-dashboards', () => {
        return HttpResponse.json({
          ...pubdashResponse,
          timeSelectionEnabled: false,
        });
      })
    );

    await renderSharePublicDashboard();
    await userEvent.click(screen.getByText('Settings'));

    const enableTimeRangeSwitch = await screen.findByTestId(selectors.EnableTimeRangeSwitch);
    await waitFor(() => {
      expect(enableTimeRangeSwitch).toBeEnabled();
      expect(enableTimeRangeSwitch).not.toBeChecked();
    });
  });
  it('when pubdash is enabled, then link url is available', async () => {
    await renderSharePublicDashboard();
    expect(screen.getByTestId(selectors.CopyUrlInput)).toBeInTheDocument();
  });
  it('when pubdash is disabled in the db, then link url is not copyable and switch is checked', async () => {
    server.use(
      http.get('/api/dashboards/uid/:dashboardUid/public-dashboards', ({ request }) => {
        const url = new URL(request.url);
        const dashboardUid = url.searchParams.get('dashboardUid');
        return HttpResponse.json({
          isEnabled: false,
          annotationsEnabled: false,
          uid: 'a-uid',
          dashboardUid,
          accessToken: 'an-access-token',
        });
      })
    );

    await renderSharePublicDashboard();

    expect(await screen.findByTestId(selectors.CopyUrlInput)).toBeInTheDocument();
    expect(screen.queryByTestId(selectors.CopyUrlButton)).not.toBeChecked();

    expect(screen.getByTestId(selectors.PauseSwitch)).toBeChecked();
  });
  it('does not render email sharing section', async () => {
    await renderSharePublicDashboard();

    expect(screen.queryByTestId(selectors.EmailSharingConfiguration.EmailSharingInput)).not.toBeInTheDocument();
    expect(screen.queryByTestId(selectors.EmailSharingConfiguration.EmailSharingInviteButton)).not.toBeInTheDocument();
    expect(screen.queryByTestId(selectors.EmailSharingConfiguration.EmailSharingList)).not.toBeInTheDocument();
  });
  alertTests();
});

describe('SharePublic - Report interactions', () => {
  beforeEach(() => {
    jest.clearAllMocks();
    server.use(getExistentPublicDashboardResponse());
    server.use(
      http.patch('/api/dashboards/uid/:dashboardUid/public-dashboards/:uid', ({ request }) => {
        const url = new URL(request.url);
        const dashboardUid = url.searchParams.get('dashboardUid');
        return HttpResponse.json({
          ...pubdashResponse,
          dashboardUid,
        });
      })
    );
  });

  it('reports interaction when public dashboard tab is clicked', async () => {
    jest.spyOn(DashboardInteractions, 'sharingCategoryClicked');
    await renderSharePublicDashboard();

    await waitFor(() => {
      expect(DashboardInteractions.sharingCategoryClicked).lastCalledWith({
        item: shareDashboardType.publicDashboard,
        shareResource: 'dashboard',
      });
    });
  });

  it('reports interaction when time range is clicked', async () => {
    await renderSharePublicDashboard();
    await userEvent.click(screen.getByText('Settings'));

    await waitFor(() => {
      expect(screen.getByTestId(selectors.EnableTimeRangeSwitch)).toBeEnabled();
    });
    await userEvent.click(screen.getByTestId(selectors.EnableTimeRangeSwitch));

    await waitFor(() => {
      expect(reportInteraction).toHaveBeenLastCalledWith('dashboards_sharing_public_time_picker_clicked', {
        enabled: !pubdashResponse.timeSelectionEnabled,
      });
    });
  });

  it('reports interaction when show annotations is clicked', async () => {
    await renderSharePublicDashboard();
    await userEvent.click(screen.getByText('Settings'));

    await waitFor(() => {
      expect(screen.getByTestId(selectors.EnableAnnotationsSwitch)).toBeEnabled();
    });
    await userEvent.click(screen.getByTestId(selectors.EnableAnnotationsSwitch));

    await waitFor(() => {
      expect(reportInteraction).toHaveBeenLastCalledWith('dashboards_sharing_public_annotations_clicked', {
        enabled: !pubdashResponse.annotationsEnabled,
      });
    });
  });
  it('reports interaction when pause is clicked', async () => {
    await renderSharePublicDashboard();
    await waitFor(() => {
      expect(screen.getByTestId(selectors.PauseSwitch)).toBeEnabled();
    });
    await userEvent.click(screen.getByTestId(selectors.PauseSwitch));

    await waitFor(() => {
      expect(reportInteraction).toHaveBeenLastCalledWith('dashboards_sharing_public_pause_clicked', {
        paused: pubdashResponse.isEnabled,
      });
    });
  });
});
