import {
  screen,
  userEvent,
  waitForElementToBeRemoved,
  within,
} from 'sentry-test/reactTestingLibrary';
import {textWithMarkupMatcher} from 'sentry-test/utils';

import {SamplingInnerName, SamplingRuleType} from 'sentry/types/sampling';
import {
  distributedTracesConditions,
  individualTransactionsConditions,
} from 'sentry/views/settings/project/sampling/modal/utils';
import {
  getInnerNameLabel,
  LEGACY_BROWSER_LIST,
} from 'sentry/views/settings/project/sampling/utils';

import {openSamplingRuleModal, renderComponent} from './utils';

describe('Sampling - Modal', function () {
  beforeEach(function () {
    MockApiClient.addMockResponse({
      url: '/projects/org-slug/project-slug/',
      method: 'GET',
      body: TestStubs.Project(),
    });

    MockApiClient.addMockResponse({
      url: '/projects/org-slug/project-slug/tags/',
      body: TestStubs.Tags,
    });

    MockApiClient.addMockResponse({
      url: '/organizations/org-slug/tags/release/values/',
      method: 'GET',
      body: [{value: '1.2.3'}],
    });
  });

  afterEach(function () {
    MockApiClient.clearMockResponses();
  });

  it('saves distributed traces rule', async function () {
    const rule = {
      condition: {
        inner: [{name: 'trace.release', op: 'glob', value: ['1.2.3']}],
        op: 'and',
      },
      id: 0,
      sampleRate: 0.2,
      type: 'trace',
    };

    const saveMock = MockApiClient.addMockResponse({
      url: '/projects/org-slug/project-slug/',
      method: 'PUT',
      body: TestStubs.Project({
        dynamicSampling: {
          rules: [rule],
          next_id: 41,
        },
      }),
    });

    renderComponent({ruleType: SamplingRuleType.TRACE});

    // Open Modal
    await openSamplingRuleModal(screen.getByText('Add Rule'));
    const dialog = screen.getByRole('dialog');

    // Modal description
    expect(
      screen.getByText(
        textWithMarkupMatcher(
          'Using a Trace ID, select all Transactions distributed across multiple projects/services which match your conditions. However, if you only want to select Transactions from within this project, we recommend you add a Individual Transaction rule instead.'
        )
      )
    ).toBeInTheDocument();
    expect(screen.getByRole('link', {name: 'Individual Transaction'})).toHaveAttribute(
      'href',
      `${SamplingRuleType.TRANSACTION}/`
    );

    // Empty conditions message
    expect(screen.getByText('No conditions added')).toBeInTheDocument();
    expect(
      screen.getByText(
        textWithMarkupMatcher(
          "if you don't want to add (+) a condition, simply, add a sample rate below"
        )
      )
    ).toBeInTheDocument();

    // Click on 'Add condition'
    userEvent.click(screen.getByText('Add Condition'));

    // Autocomplete
    expect(screen.getByText(/filter conditions/i)).toBeInTheDocument();

    // Distributed Traces Options
    distributedTracesConditions.forEach(condition => {
      expect(within(dialog).getByText(getInnerNameLabel(condition))).toBeInTheDocument();
    });
    expect(
      within(dialog).queryByText(
        getInnerNameLabel(SamplingInnerName.EVENT_LEGACY_BROWSER)
      )
    ).not.toBeInTheDocument();

    // Click on the condition option
    userEvent.click(screen.getAllByText('Release')[0]);

    // Release field is empty
    expect(screen.queryByTestId('multivalue')).not.toBeInTheDocument();

    // Type into release field
    userEvent.paste(screen.getByLabelText('Search or add a release'), '1.2.3');

    // Autocomplete suggests options
    expect(screen.getByTestId('1.2.3')).toHaveTextContent('1.2.3');

    // Click on the suggested option
    userEvent.click(screen.getByTestId('1.2.3'));

    // Button is still disabled
    expect(screen.getByLabelText('Save Rule')).toBeDisabled();

    // Fill sample rate field
    userEvent.paste(screen.getByPlaceholderText('\u0025'), '20');

    // Save button is now enabled
    expect(screen.getByLabelText('Save Rule')).toBeEnabled();

    // Click on save button
    userEvent.click(screen.getByLabelText('Save Rule'));

    // Close Modal
    userEvent.click(screen.getByLabelText('Close Modal'));
    await waitForElementToBeRemoved(() =>
      screen.queryByText('Add Distributed Trace Rule')
    );

    expect(saveMock).toHaveBeenLastCalledWith(
      '/projects/org-slug/project-slug/',
      expect.objectContaining({
        data: {
          dynamicSampling: {
            rules: [rule],
          },
        },
      })
    );

    expect(screen.getByText('Release')).toBeInTheDocument();
    expect(screen.getByText('1.2.3')).toBeInTheDocument();
    expect(screen.getByText('20%')).toBeInTheDocument();
  });

  it('saves individual transactions rule', async function () {
    const rule = {
      condition: {
        inner: [{name: 'event.release', op: 'glob', value: ['1.2.3']}],
        op: 'and',
      },
      id: 0,
      sampleRate: 0.3,
      type: 'transaction',
    };

    const saveMock = MockApiClient.addMockResponse({
      url: '/projects/org-slug/project-slug/',
      method: 'PUT',
      body: TestStubs.Project({
        dynamicSampling: {
          rules: [rule],
          next_id: 41,
        },
      }),
    });

    renderComponent({ruleType: SamplingRuleType.TRANSACTION});

    // Open Modal
    await openSamplingRuleModal(screen.getByText('Add Rule'));
    const dialog = screen.getByRole('dialog');

    // Modal description
    expect(
      screen.getByText(
        textWithMarkupMatcher(
          'Select Transactions only within this project which match your conditions. However, If you want to select all Transactions distributed across multiple projects/services, we recommend you add a Distributed Trace rule instead.'
        )
      )
    ).toBeInTheDocument();
    expect(screen.getByRole('link', {name: 'Distributed Trace'})).toHaveAttribute(
      'href',
      `${SamplingRuleType.TRACE}/`
    );

    // Empty conditions message
    expect(screen.getByText('No conditions added')).toBeInTheDocument();
    expect(
      screen.getByText(
        textWithMarkupMatcher(
          "if you don't want to add (+) a condition, simply, add a sample rate below"
        )
      )
    ).toBeInTheDocument();

    // Click on 'Add condition'
    userEvent.click(screen.getByText('Add Condition'));

    // Individual Transactions Options
    individualTransactionsConditions.forEach(condition => {
      expect(within(dialog).getByText(getInnerNameLabel(condition))).toBeInTheDocument();
    });

    // Click on the first condition option
    userEvent.click(screen.getAllByText('Release')[0]);

    // Type into release field
    userEvent.paste(screen.getByLabelText('Search or add a release'), '1.2.3');

    // Click on the suggested option
    userEvent.click(screen.getByTestId('1.2.3'));

    // Fill sample rate field
    userEvent.paste(screen.getByPlaceholderText('\u0025'), '30');

    // Click on save button
    userEvent.click(screen.getByLabelText('Save Rule'));

    // Close Modal
    userEvent.click(screen.getByLabelText('Close Modal'));
    await waitForElementToBeRemoved(() =>
      screen.queryByText('Add Individual Transaction Rule')
    );

    expect(saveMock).toHaveBeenLastCalledWith(
      '/projects/org-slug/project-slug/',
      expect.objectContaining({
        data: {
          dynamicSampling: {
            rules: [rule],
          },
        },
      })
    );
  });

  it('edits the rule', async function () {
    const newRule = {
      condition: {
        inner: [{name: 'trace.release', op: 'glob', value: ['1.2.3']}],
        op: 'and',
      },
      id: 0,
      sampleRate: 0.6,
      type: 'trace',
    };

    MockApiClient.addMockResponse({
      url: '/projects/org-slug/project-slug/',
      method: 'GET',
      body: TestStubs.Project({
        dynamicSampling: {
          rules: [
            {
              sampleRate: 0.2,
              type: 'trace',
              condition: {
                op: 'and',
                inner: [
                  {
                    op: 'glob',
                    name: 'trace.release',
                    value: ['1.2.2'],
                  },
                ],
              },
              id: 40,
            },
          ],
          next_id: 41,
        },
      }),
    });

    const saveMock = MockApiClient.addMockResponse({
      url: '/projects/org-slug/project-slug/',
      method: 'PUT',
      body: TestStubs.Project({
        dynamicSampling: {
          rules: [newRule],
          next_id: 42,
        },
      }),
    });

    renderComponent();

    expect(screen.getByText('1.2.2')).toBeInTheDocument();
    expect(screen.getByText('20%')).toBeInTheDocument();

    await openSamplingRuleModal(screen.getByLabelText('Edit Rule'));

    // Empty conditions message is not displayed
    expect(screen.queryByText('No conditions added')).not.toBeInTheDocument();

    // Type into realease field
    userEvent.clear(screen.getByLabelText('Search or add a release'));
    userEvent.paste(screen.getByLabelText('Search or add a release'), '1.2.3');

    // Click on the suggested option
    userEvent.click(await screen.findByText(textWithMarkupMatcher('Add "1.2.3"')));

    // Update sample rate field
    userEvent.clear(screen.getByPlaceholderText('\u0025'));
    userEvent.paste(screen.getByPlaceholderText('\u0025'), '60');

    // Click on save button
    userEvent.click(screen.getByLabelText('Save Rule'));

    // Modal will close
    await waitForElementToBeRemoved(() =>
      screen.queryByText('Edit Distributed Trace Rule')
    );

    expect(saveMock).toHaveBeenLastCalledWith(
      '/projects/org-slug/project-slug/',
      expect.objectContaining({
        data: {
          dynamicSampling: {
            rules: [newRule],
          },
        },
      })
    );

    // Old values
    expect(screen.queryByText('1.2.2')).not.toBeInTheDocument();
    expect(screen.queryByText('20%')).not.toBeInTheDocument();

    // New values
    expect(screen.getByText('1.2.3')).toBeInTheDocument();
    expect(screen.getByText('60%')).toBeInTheDocument();
  });

  it('legacy browsers condition', async function () {
    const rule = {
      condition: {
        inner: [
          {
            name: 'event.legacy_browser',
            op: 'custom',
            value: [
              'ie_pre_9',
              'ie9',
              'ie10',
              'ie11',
              'safari_pre_6',
              'opera_pre_15',
              'opera_mini_pre_8',
              'android_pre_4',
            ],
          },
        ],
        op: 'and',
      },
      id: 0,
      sampleRate: 0.2,
      type: 'transaction',
    };

    const saveMock = MockApiClient.addMockResponse({
      url: '/projects/org-slug/project-slug/',
      method: 'PUT',
      body: TestStubs.Project({
        dynamicSampling: {
          rules: [rule],
          next_id: 43,
        },
      }),
    });

    renderComponent({ruleType: SamplingRuleType.TRANSACTION});

    // Open Modal
    await openSamplingRuleModal(screen.getByText('Add Rule'));

    // Click on 'Add condition'
    userEvent.click(screen.getByText('Add Condition'));

    // Select Legacy Browser
    userEvent.click(screen.getByText('Legacy Browser'));

    // Legacy Browsers
    expect(screen.getByText('All browsers')).toBeInTheDocument();

    const legacyBrowsers = Object.keys(LEGACY_BROWSER_LIST);
    for (const legacyBrowser of legacyBrowsers) {
      const {icon, title} = LEGACY_BROWSER_LIST[legacyBrowser];
      expect(screen.getByText(title)).toBeInTheDocument();
      expect(screen.getAllByTestId(`icon-${icon}`)[0]).toBeInTheDocument();
    }

    expect(screen.getAllByTestId('icon-internet-explorer')).toHaveLength(4);
    expect(screen.getAllByTestId('icon-opera')).toHaveLength(2);
    expect(screen.getByTestId('icon-safari')).toBeInTheDocument();
    expect(screen.getByTestId('icon-android')).toBeInTheDocument();

    const switchButtons = screen.getAllByTestId('switch');
    expect(switchButtons).toHaveLength(legacyBrowsers.length + 1);

    // All browsers are unchecked
    for (const switchButton of switchButtons) {
      expect(switchButton).not.toBeChecked();
    }

    // Click on the switch of 'All browsers' option
    userEvent.click(switchButtons[0]);

    // All browsers are now checked
    for (const switchButton of switchButtons) {
      expect(switchButton).toBeChecked();
    }

    // Fill sample rate field
    userEvent.paste(screen.getByPlaceholderText('\u0025'), '20');

    // Click on save button
    userEvent.click(screen.getByLabelText('Save Rule'));

    // Close Modal
    userEvent.click(screen.getByLabelText('Close Modal'));
    await waitForElementToBeRemoved(() =>
      screen.queryByText('Add Individual Transaction Rule')
    );

    expect(saveMock).toHaveBeenLastCalledWith(
      '/projects/org-slug/project-slug/',
      expect.objectContaining({
        data: {
          dynamicSampling: {
            rules: [rule],
          },
        },
      })
    );

    // Transaction rules panel is updated
    expect(screen.getByText('Legacy Browser')).toBeInTheDocument();
    for (const legacyBrowser of legacyBrowsers) {
      const {title} = LEGACY_BROWSER_LIST[legacyBrowser];
      expect(screen.getByText(title)).toBeInTheDocument();
    }
  });

  it('custom tag condition', async function () {
    const rule = {
      condition: {
        inner: [{name: 'event.tags.user', op: 'glob', value: ['david']}],
        op: 'and',
      },
      id: 0,
      sampleRate: 0.15,
      type: 'transaction',
    };

    MockApiClient.addMockResponse({
      url: '/organizations/org-slug/tags/user/values/',
      method: 'GET',
      body: [{value: 'david'}],
    });

    const saveMock = MockApiClient.addMockResponse({
      url: '/projects/org-slug/project-slug/',
      method: 'PUT',
      body: TestStubs.Project({
        dynamicSampling: {
          rules: [rule],
          next_id: 43,
        },
      }),
    });

    renderComponent({ruleType: SamplingRuleType.TRANSACTION});

    // Open Modal
    await openSamplingRuleModal(screen.getByText('Add Rule'));

    // Click on 'Add condition'
    userEvent.click(screen.getByText('Add Condition'));

    // Select Custom Tag
    userEvent.click(screen.getByText('Add Custom Tag'));

    // Type into tag field
    userEvent.paste(screen.getByLabelText('Search or add a tag'), 'user');

    // Click on the suggested option
    userEvent.click(screen.getByTestId('user'));

    // Type into tag value field
    userEvent.paste(screen.getByLabelText('Search or add tag values'), 'david');

    // Click on the suggested option
    userEvent.click(screen.getByTestId('david'));

    // Fill sample rate field
    userEvent.paste(screen.getByPlaceholderText('\u0025'), '15');

    // Click on 'Add condition'
    userEvent.click(screen.getByText('Add Condition'));

    // Custom tag condition is added to the conditions dropdown
    expect(screen.getByText('user - Custom')).toBeInTheDocument();

    // Click on save button
    userEvent.click(screen.getByLabelText('Save Rule'));

    // Close Modal
    userEvent.click(screen.getByLabelText('Close Modal'));
    await waitForElementToBeRemoved(() =>
      screen.queryByText('Add Individual Transaction Rule')
    );

    expect(saveMock).toHaveBeenLastCalledWith(
      '/projects/org-slug/project-slug/',
      expect.objectContaining({
        data: {
          dynamicSampling: {
            rules: [rule],
          },
        },
      })
    );
  });

  it('invalid custom tag condition', async function () {
    MockApiClient.addMockResponse({
      url: '/organizations/org-slug/tags/sentry.key/values/',
      method: 'GET',
      body: [],
    });

    renderComponent({ruleType: SamplingRuleType.TRANSACTION});

    // Open Modal
    await openSamplingRuleModal(screen.getByText('Add Rule'));

    // Click on 'Add condition'
    userEvent.click(screen.getByText('Add Condition'));

    // Select Custom Tag
    userEvent.click(screen.getByText('Add Custom Tag'));

    // Type invalid value into tag field
    userEvent.paste(screen.getByLabelText('Search or add a tag'), 'sentry.*');

    // Dropdown display 'no options' because the tag is invalid
    expect(await screen.findByText('No options')).toBeInTheDocument();

    // Type valid value into tag field
    userEvent.type(screen.getByLabelText('Search or add a tag'), '{backspace}key');

    // Click on the suggested option
    userEvent.click(screen.getByTestId('sentry.key'));

    // Type invalid value into tag value field
    userEvent.paste(screen.getByLabelText('Search or add tag values'), 'invalid\\nvalue');

    // Dropdown display 'no options' because the tag value is invalid
    expect(await screen.findByText('No options')).toBeInTheDocument();

    // Clears tag value field
    userEvent.clear(screen.getByLabelText('Search or add tag values'));

    // Type valid value into tag value field
    userEvent.paste(screen.getByLabelText('Search or add tag values'), 'valid');

    // Click on the suggested option
    userEvent.click(screen.getByTestId('valid'));
  });

  it('does not let you save without permissions', async function () {
    renderComponent({
      orgOptions: {
        features: ['filters-and-sampling'],
        access: [],
      },
    });

    // Open Modal
    await openSamplingRuleModal(screen.getByText('Add Rule'));

    // Click on 'Add condition'
    userEvent.click(screen.getByText('Add Condition'));

    // Click on the condition option
    userEvent.click(screen.getAllByText('Release')[0]);

    // Type into release field
    userEvent.paste(screen.getByLabelText('Search or add a release'), '1.2.3');

    // Click on the suggested option
    userEvent.click(screen.getByTestId('1.2.3'));

    // Fill sample rate field
    userEvent.paste(screen.getByPlaceholderText('\u0025'), '20');

    // Button is still disabled
    expect(screen.getByLabelText('Save Rule')).toBeDisabled();

    // Close Modal
    userEvent.click(screen.getByLabelText('Close Modal'));
    await waitForElementToBeRemoved(() =>
      screen.queryByText('Add Distributed Trace Rule')
    );
  });

  it('does not let you save a distributed trace rule without a condition, if a trace rule without a condition already exists', async function () {
    MockApiClient.addMockResponse({
      url: '/projects/org-slug/project-slug/',
      method: 'GET',
      body: TestStubs.Project({
        dynamicSampling: {
          rules: [
            {
              sampleRate: 0.2,
              type: 'trace',
              condition: {
                op: 'and',
                inner: [
                  {
                    op: 'glob',
                    name: 'trace.release',
                    value: ['1.2.2'],
                  },
                ],
              },
              id: 40,
            },
            {
              sampleRate: 0.5,
              type: 'trace',
              condition: {
                op: 'and',
                inner: [],
              },
              id: 41,
            },
          ],
          next_id: 42,
        },
      }),
    });

    renderComponent();

    // Open Modal
    await openSamplingRuleModal(screen.getByText('Add Rule'));

    // Empty conditions message
    expect(screen.getByText('No conditions added')).toBeInTheDocument();

    // A hint about an existing 'sample all' rule is displayed
    expect(
      screen.getByText(
        'A rule with no conditions already exists. You can edit that existing rule or add a condition to this rule'
      )
    ).toBeInTheDocument();

    // Adds a sample rate
    userEvent.paste(screen.getByPlaceholderText('\u0025'), '5');

    // Button is still disabled
    expect(screen.getByLabelText('Save Rule')).toBeDisabled();
  });

  it('does not let you save a individual transaction rule without a condition, if a transaction rule without a condition already exists', async function () {
    MockApiClient.addMockResponse({
      url: '/projects/org-slug/project-slug/',
      method: 'GET',
      body: TestStubs.Project({
        dynamicSampling: {
          rules: [
            {
              condition: {
                inner: [{name: 'event.release', op: 'glob', value: ['1.2.3']}],
                op: 'and',
              },
              id: 0,
              sampleRate: 0.3,
              type: 'transaction',
            },
            {
              condition: {
                inner: [],
                op: 'and',
              },
              id: 1,
              sampleRate: 0.2,
              type: 'transaction',
            },
          ],
          next_id: 2,
        },
      }),
    });

    renderComponent({ruleType: SamplingRuleType.TRANSACTION});

    // Open Modal
    await openSamplingRuleModal(screen.getByText('Add Rule'));

    // Empty conditions message
    expect(screen.getByText('No conditions added')).toBeInTheDocument();

    // A hint about an existing 'sample all' rule is displayed
    expect(
      screen.getByText(
        'A rule with no conditions already exists. You can edit that existing rule or add a condition to this rule'
      )
    ).toBeInTheDocument();

    // Adds a sample rate
    userEvent.paste(screen.getByPlaceholderText('\u0025'), '5');

    // Button is still disabled
    expect(screen.getByLabelText('Save Rule')).toBeDisabled();
  });
});
