import { within } from '@testing-library/react';
import { render, screen, userEvent, waitFor } from 'test/test-utils';
import { byLabelText, byRole, byText } from 'testing-library-selector';

import { setPluginLinksHook } from '@grafana/runtime';
import server from '@grafana/test-utils/server';
import { mockAlertRuleApi, setupMswServer } from 'app/features/alerting/unified/mockApi';
import { type AlertManagerDataSourceJsonData, AlertState } from 'app/plugins/datasource/alertmanager/types';
import { AccessControlAction } from 'app/types/accessControl';
import { type CombinedRule, type RuleIdentifier } from 'app/types/unified-alerting';
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';

import {
  __clearRuleViewTabsForTests,
  addEnrichmentSection,
} from '../../enterprise-components/rule-view-page/extensions';
import {
  getCloudRule,
  getGrafanaRule,
  getVanillaPromRule,
  grantUserPermissions,
  mockAlertmanagerAlert,
  mockCombinedCloudRuleNamespace,
  mockDataSource,
  mockPluginLinkExtension,
  mockPromAlertingRule,
  mockRulerGrafanaRecordingRule,
  mockRulerGrafanaRule,
} from '../../mocks';
import { grafanaRulerRule } from '../../mocks/grafanaRulerApi';
import { setAlertmanagerAlertsHandler } from '../../mocks/server/configure';
import { grantPermissionsHelper } from '../../test/test-utils';
import { setupDataSources } from '../../testSetup/datasources';
import { Annotation } from '../../utils/constants';
import { DataSourceType } from '../../utils/datasource';
import { GRAFANA_ORIGIN_LABEL } from '../../utils/labels';
import * as ruleId from '../../utils/rule-id';
import { stringifyIdentifier } from '../../utils/rule-id';

import { AlertRuleProvider } from './RuleContext';
import RuleViewer, { ActiveTab } from './RuleViewer';
import { addRulePageEnrichmentSection } from './tabs/extensions/RuleViewerExtension';

jest.mock('@grafana/assistant', () => ({
  useAssistant: () => ({ isAvailable: false, openAssistant: jest.fn() }),
}));

// metadata and interactive elements
const ELEMENTS = {
  loading: byText(/Loading rule/i),
  metadata: {
    summary: (text: string) => byText(text),
    runbook: (url: string) => byRole('link', { name: url }),
    dashboardAndPanel: byRole('link', { name: 'View panel' }),
    evaluationInterval: (interval: string) => byText(`Every ${interval}`),
    label: ([key, value]: [string, string]) => byRole('listitem', { name: `${key}: ${value}` }),
  },
  details: {
    pendingPeriod: byLabelText(/Pending period/i),
  },
  actions: {
    edit: byRole('link', { name: 'Edit' }),
    more: {
      button: byRole('button', { name: /More/i }),
      actions: {
        silence: byRole('menuitem', { name: /Silence/i }),
        duplicate: byRole('menuitem', { name: /Duplicate/i }),
        copyLink: byRole('menuitem', { name: /Copy link/i }),
        export: byRole('menuitem', { name: /Export/i }),
        delete: byRole('menuitem', { name: /Delete/i }),
      },
      pluginActions: {
        sloDashboard: byRole('menuitem', { name: /SLO dashboard/i }),
        declareIncident: byRole('menuitem', { name: /Declare incident/i }),
        assertsWorkbench: byRole('menuitem', { name: /Open workbench/i }),
      },
    },
  },
};

setupMswServer();

setPluginLinksHook(() => ({
  links: [
    mockPluginLinkExtension({ pluginId: 'grafana-slo-app', title: 'SLO dashboard', path: '/a/grafana-slo-app' }),
    mockPluginLinkExtension({
      pluginId: 'grafana-asserts-app',
      title: 'Open workbench',
      path: '/a/grafana-asserts-app',
    }),
  ],
  isLoading: false,
}));

const openSilenceDrawer = async () => {
  const user = userEvent.setup();
  await user.click(ELEMENTS.actions.more.button.get());
  await user.click(ELEMENTS.actions.more.actions.silence.get());
  await screen.findByText(/Configure silences/i);
};

beforeAll(() => {
  // Register the enrichment tab for all tests
  addEnrichmentSection();
});

afterEach(() => {
  // Clear tabs after each test to prevent interference
  __clearRuleViewTabsForTests();
  // Re-register for next test
  addEnrichmentSection();
});

beforeEach(() => {
  grantPermissionsHelper([
    AccessControlAction.AlertingRuleCreate,
    AccessControlAction.AlertingRuleRead,
    AccessControlAction.AlertingRuleUpdate,
    AccessControlAction.AlertingRuleDelete,
    AccessControlAction.AlertingInstanceCreate,
  ]);
});

const dataSources = {
  am: mockDataSource<AlertManagerDataSourceJsonData>(
    {
      name: 'Alertmanager',
      type: DataSourceType.Alertmanager,
      jsonData: { handleGrafanaManagedAlerts: true },
    },
    { module: 'core:plugin/alertmanager' }
  ),
  mimir: mockDataSource({ uid: 'mimir', name: 'Mimir' }, { module: 'core:plugin/prometheus' }),
  prometheus: mockDataSource({ uid: 'prometheus', name: 'Prometheus' }, { module: 'core:plugin/prometheus' }),
};

describe('RuleViewer', () => {
  const api = mockAlertRuleApi(server);

  beforeEach(() => {
    setupDataSources(...Object.values(dataSources));
  });

  describe('Grafana managed alert rule', () => {
    const mockRule = getGrafanaRule(
      {
        name: 'Test alert',
        annotations: {
          [Annotation.dashboardUID]: 'dashboard-1',
          [Annotation.panelID]: 'panel-1',
          [Annotation.summary]: 'This is the summary for the rule',
          [Annotation.runbookURL]: 'https://runbook.site/',
        },
        labels: {
          team: 'operations',
          severity: 'low',
        },
        group: {
          name: 'my-group',
          interval: '15m',
          rules: [],
          totals: { alerting: 1 },
        },
      },
      { uid: grafanaRulerRule.grafana_alert.uid }
    );
    const mockRuleIdentifier = ruleId.fromCombinedRule('grafana', mockRule);

    beforeEach(() => {
      grantPermissionsHelper([
        AccessControlAction.AlertingRuleCreate,
        AccessControlAction.AlertingRuleRead,
        AccessControlAction.AlertingRuleUpdate,
        AccessControlAction.AlertingRuleDelete,
        AccessControlAction.AlertingInstanceRead,
        AccessControlAction.AlertingInstanceCreate,
        AccessControlAction.AlertingInstanceRead,
        AccessControlAction.AlertingInstancesExternalRead,
        AccessControlAction.AlertingInstancesExternalWrite,
      ]);
    });

    it('should render a Grafana managed alert rule', async () => {
      await renderRuleViewer(mockRule, mockRuleIdentifier);

      // assert on basic info to be visible
      expect(screen.getByText('Test alert')).toBeInTheDocument();
      expect(screen.getByText('Firing')).toBeInTheDocument();

      // alert rule metadata
      const ruleSummary = mockRule.annotations[Annotation.summary];
      const runBookURL = mockRule.annotations[Annotation.runbookURL];
      const groupInterval = mockRule.group.interval;
      const labels = mockRule.labels;

      expect(ELEMENTS.metadata.summary(ruleSummary).get()).toBeInTheDocument();
      expect(ELEMENTS.metadata.dashboardAndPanel.get()).toBeInTheDocument();
      expect(ELEMENTS.metadata.runbook(runBookURL).get()).toBeInTheDocument();
      expect(ELEMENTS.metadata.evaluationInterval(groupInterval!).get()).toBeInTheDocument();

      for (const label in labels) {
        expect(ELEMENTS.metadata.label([label, labels[label]]).get()).toBeInTheDocument();
      }

      // actions
      expect(await ELEMENTS.actions.edit.find()).toBeInTheDocument();
      expect(ELEMENTS.actions.more.button.get()).toBeInTheDocument();

      // check the "more actions" button
      await userEvent.click(ELEMENTS.actions.more.button.get());
      const menuItems = Object.values(ELEMENTS.actions.more.actions);
      for (const menuItem of menuItems) {
        expect(menuItem.get()).toBeInTheDocument();
      }
    });

    it('shows paused state correctly for recording rules', async () => {
      const recordingRule = getGrafanaRule({
        name: 'Test recording rule',
        rulerRule: mockRulerGrafanaRecordingRule(
          {},
          {
            is_paused: true,
            title: 'Test recording',
            record: {
              metric: 'test_recording',
              from: 'A',
            },
          }
        ),
      });

      const recordingRuleIdentifier = ruleId.fromCombinedRule('grafana', recordingRule);
      await renderRuleViewer(recordingRule, recordingRuleIdentifier);

      expect(await screen.findByText('Test recording rule')).toBeInTheDocument();
      expect(await screen.findByRole('status', { name: 'Alert evaluation currently paused' })).toBeInTheDocument();
      expect(screen.queryByText(/last evaluation duration/i)).not.toBeInTheDocument();
    });

    it('renders silencing form correctly and shows alert rule name', async () => {
      await renderRuleViewer(mockRule, mockRuleIdentifier);
      await openSilenceDrawer();

      const silenceDrawer = await screen.findByRole('dialog', { name: 'Silence alert rule' });
      expect(await within(silenceDrawer).findByLabelText(/^alert rule/i)).toHaveValue(
        grafanaRulerRule.grafana_alert.title
      );
    });

    describe('version history', () => {
      it('renders version history tab, and the compare version is enabled only when we have 2 versions selected', async () => {
        const { user } = await renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.VersionHistory);

        expect(await screen.findByRole('button', { name: /Compare versions/i })).toBeDisabled();

        expect(screen.getAllByRole('row')).toHaveLength(7);
        expect(screen.getAllByRole('row')[1]).toHaveTextContent(/6Provisioning2025-01-18 04:35:17/i);
        expect(screen.getAllByRole('row')[1]).toHaveTextContent('Updated by provisioning service');
        expect(screen.getAllByRole('row')[1]).toHaveTextContent('+4-3Latest');

        expect(screen.getAllByRole('row')[2]).toHaveTextContent(/5Alerting2025-01-17 04:35:17/i);
        expect(screen.getAllByRole('row')[2]).toHaveTextContent('+5-6');

        expect(screen.getAllByRole('row')[3]).toHaveTextContent(/4different user2025-01-16 04:35:17/i);
        expect(screen.getAllByRole('row')[3]).toHaveTextContent('Changed alert title and thresholds');
        expect(screen.getAllByRole('row')[3]).toHaveTextContent('+6-5');

        expect(screen.getAllByRole('row')[4]).toHaveTextContent(/3user12025-01-15 04:35:17/i);
        expect(screen.getAllByRole('row')[4]).toHaveTextContent('+5-10');

        expect(screen.getAllByRole('row')[5]).toHaveTextContent(/2User ID foo2025-01-14 04:35:17/i);
        expect(screen.getAllByRole('row')[5]).toHaveTextContent('Updated evaluation interval and routing');
        expect(screen.getAllByRole('row')[5]).toHaveTextContent('+12-7');

        expect(screen.getAllByRole('row')[6]).toHaveTextContent(/1Unknown 2025-01-13 04:35:17/i);

        await user.click(screen.getByLabelText('1'));
        await user.click(screen.getByLabelText('2'));
        expect(await screen.findByRole('button', { name: /Compare versions/i })).toBeEnabled();
        await user.click(screen.getByLabelText('1'));
        expect(await screen.findByRole('button', { name: /Compare versions/i })).toBeDisabled();
      });
      it('shows version history with special case `updated_by` values', async () => {
        await renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.VersionHistory);
        expect(await screen.findByRole('button', { name: /Compare versions/i })).toBeDisabled();

        // Check for special updated_by values - use getAllByRole since some text appears in multiple columns
        expect(screen.getAllByRole('cell', { name: /provisioning/i }).length).toBeGreaterThan(0);
        expect(screen.getByRole('cell', { name: /^alerting$/i })).toBeInTheDocument();
        expect(screen.getByRole('cell', { name: /^Unknown$/i })).toBeInTheDocument();
        expect(screen.getByRole('cell', { name: /user id foo/i })).toBeInTheDocument();
      });

      it('shows comparison of versions', async () => {
        const { user } = await renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.VersionHistory);
        expect(await screen.findByRole('button', { name: /Compare versions/i })).toBeDisabled();

        await user.click(screen.getByLabelText('1'));
        await user.click(screen.getByLabelText('2'));
        await user.click(screen.getByRole('button', { name: /Compare versions/i }));
        await screen.findByText(/comparing versions/i);
        // "Pending period" appears in both the always-visible sidebar and the version diff
        expect((await screen.findAllByText(/pending period/i)).length).toBeGreaterThanOrEqual(1);
        expect(screen.getAllByTestId('diffGroup')[0]).toHaveTextContent(/pending period changed 5m2h/i);
        expect(screen.getAllByTestId('diffGroup')[1]).toHaveTextContent(/labels added foo bar/i);
        expect(screen.getAllByTestId('diffGroup')[2]).toHaveTextContent(/contact point routing added/i);
      });

      it('renders version summary correctly for special cases', async () => {
        const { user } = await renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.VersionHistory);
        expect(await screen.findByRole('button', { name: /Compare versions/i })).toBeDisabled();

        await user.click(screen.getByLabelText('6'));
        await user.click(screen.getByLabelText('5'));
        await user.click(screen.getByRole('button', { name: /Compare versions/i }));
        await screen.findByText(/comparing versions/i);

        const versionSummary = screen.getByRole('heading', { level: 4 });
        expect(versionSummary).toHaveTextContent(/Version 5 updated by alerting/i);
        expect(versionSummary).toHaveTextContent(/Version 6 updated by provisioning/i);
      });

      it('should not show any labels if we only have private labels', async () => {
        const ruleIdentifier = ruleId.fromCombinedRule('grafana', mockRule);
        const rule = getGrafanaRule({
          name: 'Test alert',
          labels: {
            [GRAFANA_ORIGIN_LABEL]: 'plugins/synthetic-monitoring-app',
          },
        });

        await renderRuleViewer(rule, ruleIdentifier);
        expect(screen.queryByText('Labels')).not.toBeInTheDocument();
      });

      it('shows Notes column when versions have messages', async () => {
        await renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.VersionHistory);

        expect(await screen.findByRole('columnheader', { name: /Notes/i })).toBeInTheDocument();
        expect(screen.getAllByRole('row')).toHaveLength(7); // 1 header + 6 data rows
        expect(screen.getByRole('cell', { name: /Updated by provisioning service/i })).toBeInTheDocument();
        expect(screen.getByRole('cell', { name: /Changed alert title and thresholds/i })).toBeInTheDocument();
        expect(screen.getByRole('cell', { name: /Updated evaluation interval and routing/i })).toBeInTheDocument();
      });

      it('does not show Notes column when no versions have messages', async () => {
        const versionsWithoutMessages = [
          mockRulerGrafanaRule(
            {},
            {
              uid: grafanaRulerRule.grafana_alert.uid,
              version: 2,
              updated: '2025-01-14T09:35:17.000Z',
              updated_by: { uid: 'foo', name: '' },
            }
          ),
          mockRulerGrafanaRule(
            {},
            {
              uid: grafanaRulerRule.grafana_alert.uid,
              version: 1,
              updated: '2025-01-13T09:35:17.000Z',
              updated_by: null,
            }
          ),
        ];
        api.getAlertRuleVersionHistory(grafanaRulerRule.grafana_alert.uid, versionsWithoutMessages);

        await renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.VersionHistory);

        await screen.findByRole('button', { name: /Compare versions/i });

        expect(screen.getAllByRole('row')).toHaveLength(3); // 1 header + 2 data rows
        expect(screen.queryByRole('columnheader', { name: /Notes/i })).not.toBeInTheDocument();
      });
    });
  });

  describe('inhibition status in title', () => {
    const mockRule = getGrafanaRule(
      {
        name: 'Test alert',
        promRule: mockPromAlertingRule({ state: PromAlertingRuleState.Firing }),
      },
      { uid: grafanaRulerRule.grafana_alert.uid }
    );
    const mockRuleIdentifier = ruleId.fromCombinedRule('grafana', mockRule);

    beforeEach(() => {
      grantPermissionsHelper([
        AccessControlAction.AlertingRuleRead,
        AccessControlAction.AlertingRuleUpdate,
        AccessControlAction.AlertingInstanceRead,
      ]);
    });

    it('should show "Inhibited" state in the title when the rule has inhibited instances', async () => {
      setAlertmanagerAlertsHandler([
        mockAlertmanagerAlert({
          labels: { __alert_rule_uid__: grafanaRulerRule.grafana_alert.uid, alertname: 'Test alert' },
          status: { state: AlertState.Suppressed, silencedBy: [], inhibitedBy: ['source-fingerprint'] },
        }),
      ]);

      await renderRuleViewer(mockRule, mockRuleIdentifier);

      expect(await screen.findByText('Inhibited')).toBeInTheDocument();
    });

    it('should not show "Inhibited" state in the title when no instances are inhibited', async () => {
      setAlertmanagerAlertsHandler([]);

      await renderRuleViewer(mockRule, mockRuleIdentifier);

      // wait for the page to settle
      await screen.findByText('Test alert');
      expect(screen.queryByText('Inhibited')).not.toBeInTheDocument();
    });

    it('should not show "Inhibited" when inhibited alerts belong to a different rule', async () => {
      setAlertmanagerAlertsHandler([
        mockAlertmanagerAlert({
          labels: { __alert_rule_uid__: 'different-rule-uid', alertname: 'Other alert' },
          status: { state: AlertState.Suppressed, silencedBy: [], inhibitedBy: ['source-fingerprint'] },
        }),
      ]);

      await renderRuleViewer(mockRule, mockRuleIdentifier);

      await screen.findByText('Test alert');
      expect(screen.queryByText('Inhibited')).not.toBeInTheDocument();
    });
  });

  describe('Data source managed alert rule', () => {
    const { mimir } = dataSources;

    const mockRule = getCloudRule(
      {
        name: 'cloud test alert',
        annotations: { [Annotation.summary]: 'cloud summary', [Annotation.runbookURL]: 'https://runbook.example.com' },
        group: { name: 'Cloud group', interval: '15m', rules: [], totals: { alerting: 1 } },
      },
      { rulesSource: mimir }
    );
    const mockRuleIdentifier = ruleId.fromCombinedRule(mimir.name, mockRule);

    beforeAll(() => {
      grantUserPermissions([
        AccessControlAction.AlertingRuleExternalRead,
        AccessControlAction.AlertingRuleExternalWrite,
      ]);
    });

    it('should render a data source managed alert rule', () => {
      renderRuleViewer(mockRule, mockRuleIdentifier);

      // assert on basic info to be vissible
      expect(screen.getByText('cloud test alert')).toBeInTheDocument();
      expect(screen.getByText('Firing')).toBeInTheDocument();

      // summary appears in both the page header and the always-visible sidebar
      expect(screen.getAllByText(mockRule.annotations[Annotation.summary])[0]).toBeInTheDocument();
      // runbook URL appears in both the page header and the always-visible sidebar
      expect(screen.getAllByRole('link', { name: mockRule.annotations[Annotation.runbookURL] })[0]).toBeInTheDocument();
      expect(screen.getByText(`Every ${mockRule.group.interval}`)).toBeInTheDocument();
    });

    it('should render custom plugin actions for a plugin-provided rule', async () => {
      const sloRule = getCloudRule(
        { name: 'slo test alert', labels: { __grafana_origin: 'plugin/grafana-slo-app' } },
        { rulesSource: mimir }
      );
      const sloRuleIdentifier = ruleId.fromCombinedRule(mimir.name, sloRule);

      const user = userEvent.setup();

      renderRuleViewer(sloRule, sloRuleIdentifier);

      expect(ELEMENTS.actions.more.button.get()).toBeInTheDocument();

      await user.click(ELEMENTS.actions.more.button.get());

      expect(ELEMENTS.actions.more.pluginActions.sloDashboard.get()).toBeInTheDocument();
      expect(ELEMENTS.actions.more.pluginActions.assertsWorkbench.query()).not.toBeInTheDocument();

      await waitFor(() => expect(ELEMENTS.actions.more.pluginActions.declareIncident.get()).toBeEnabled());
    });

    it('should render different custom plugin actions for a different plugin-provided rule', async () => {
      const assertsRule = getCloudRule(
        { name: 'asserts test alert', labels: { __grafana_origin: 'plugin/grafana-asserts-app' } },
        { rulesSource: mimir }
      );
      const assertsRuleIdentifier = ruleId.fromCombinedRule(mimir.name, assertsRule);

      renderRuleViewer(assertsRule, assertsRuleIdentifier);

      expect(ELEMENTS.actions.more.button.get()).toBeInTheDocument();

      await userEvent.click(ELEMENTS.actions.more.button.get());

      expect(ELEMENTS.actions.more.pluginActions.assertsWorkbench.get()).toBeInTheDocument();
      expect(ELEMENTS.actions.more.pluginActions.sloDashboard.query()).not.toBeInTheDocument();

      await waitFor(() => expect(ELEMENTS.actions.more.pluginActions.declareIncident.get()).toBeEnabled());
    });
  });

  describe('Vanilla Prometheus rule', () => {
    const { prometheus } = dataSources;

    const mockRule = getVanillaPromRule({
      name: 'prom test alert',
      namespace: mockCombinedCloudRuleNamespace({ name: 'prometheus' }, prometheus.name),
      annotations: {
        [Annotation.summary]: 'prom summary',
        [Annotation.runbookURL]: 'https://runbook.example.com',
      },
      promRule: {
        ...mockPromAlertingRule({
          annotations: {
            [Annotation.summary]: 'prom summary',
            [Annotation.runbookURL]: 'https://runbook.example.com',
          },
        }),
        duration: 900, // 15 minutes
      },
    });

    const mockRuleIdentifier = ruleId.fromCombinedRule(prometheus.name, mockRule);

    it('should render metadata for vanilla Prometheus alert rule', async () => {
      renderRuleViewer(mockRule, mockRuleIdentifier);

      expect(screen.getByText('prom test alert')).toBeInTheDocument();

      // Both summary and runbook are rendered by the Title component, and the DetailsTab component
      expect(ELEMENTS.metadata.summary(mockRule.annotations[Annotation.runbookURL]).getAll()).toHaveLength(2);
      expect(ELEMENTS.metadata.summary(mockRule.annotations[Annotation.summary]).getAll()).toHaveLength(2);

      expect(ELEMENTS.details.pendingPeriod.get()).toHaveTextContent(/15m/i);
    });
  });

  describe('Enrichment tab', () => {
    const mockRule = getGrafanaRule(
      {
        name: 'Test alert',
        uid: 'test-rule-uid',
        annotations: {
          [Annotation.summary]: 'This is the summary for the rule',
        },
        labels: {
          team: 'operations',
          severity: 'low',
        },
        group: {
          name: 'my-group',
          interval: '15m',
          rules: [],
          totals: { alerting: 1 },
        },
      },
      { uid: grafanaRulerRule.grafana_alert.uid }
    );
    const mockRuleIdentifier = ruleId.fromCombinedRule('grafana', mockRule);

    beforeEach(() => {
      grantPermissionsHelper([
        AccessControlAction.AlertingRuleCreate,
        AccessControlAction.AlertingRuleRead,
        AccessControlAction.AlertingRuleUpdate,
        AccessControlAction.AlertingRuleDelete,
        AccessControlAction.AlertingInstanceRead,
        AccessControlAction.AlertingInstanceCreate,
        AccessControlAction.AlertingInstancesExternalRead,
        AccessControlAction.AlertingInstancesExternalWrite,
      ]);
    });

    it('should pass correct props to enrichment section extension for editable rule', async () => {
      const mockEnrichmentExtension = jest.fn(() => <div data-testid="enrichment-section">Enrichment Section</div>);
      addRulePageEnrichmentSection(mockEnrichmentExtension);

      await renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.Enrichment);

      expect(mockEnrichmentExtension).toHaveBeenCalledWith(
        expect.objectContaining({
          ruleUid: 'test-rule-uid',
        }),
        expect.any(Object)
      );
      expect(screen.getByTestId('enrichment-section')).toBeInTheDocument();
    });

    it('should pass correct props to enrichment section extension for read-only rule', async () => {
      grantPermissionsHelper([
        AccessControlAction.AlertingRuleRead,
        AccessControlAction.AlertingInstanceRead,
        AccessControlAction.AlertingInstancesExternalRead,
      ]);

      const mockEnrichmentExtension = jest.fn(() => <div data-testid="enrichment-section">Enrichment Section</div>);
      addRulePageEnrichmentSection(mockEnrichmentExtension);

      await renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.Enrichment);

      expect(mockEnrichmentExtension).toHaveBeenCalledWith(
        expect.objectContaining({
          ruleUid: 'test-rule-uid',
        }),
        expect.any(Object)
      );
      expect(screen.getByTestId('enrichment-section')).toBeInTheDocument();
    });

    it('should show enrichment tab when user has enrichments:read permission', async () => {
      grantPermissionsHelper([AccessControlAction.AlertingRuleRead, AccessControlAction.AlertingEnrichmentsRead]);

      await renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.Query);

      // Check if enrichment tab exists in the navigation
      expect(screen.getByText('Alert enrichment')).toBeInTheDocument();
    });

    it('should hide enrichment tab when user lacks enrichments:read permission', async () => {
      grantPermissionsHelper([AccessControlAction.AlertingRuleRead]);

      await renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.Query);

      // Check that enrichment tab does not exist in the navigation
      expect(screen.queryByText('Alert enrichment')).not.toBeInTheDocument();
    });

    it('should show enrichment tab for admin users', async () => {
      grantPermissionsHelper([AccessControlAction.AlertingRuleRead]);
      jest.spyOn(require('../../utils/misc'), 'isAdmin').mockReturnValue(true);

      await renderRuleViewer(mockRule, mockRuleIdentifier, ActiveTab.Query);

      // Admin should see the tab even without explicit permission
      expect(screen.getByText('Alert enrichment')).toBeInTheDocument();
    });
  });
});

const renderRuleViewer = async (rule: CombinedRule, identifier: RuleIdentifier, tab: ActiveTab = ActiveTab.Query) => {
  const path = `/alerting/${identifier.ruleSourceName}/${stringifyIdentifier(identifier)}/view?tab=${tab}`;
  const view = render(
    <AlertRuleProvider identifier={identifier} rule={rule}>
      <RuleViewer />
    </AlertRuleProvider>,
    { historyOptions: { initialEntries: [path] } }
  );

  await waitFor(() => expect(ELEMENTS.loading.query()).not.toBeInTheDocument());

  return view;
};

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