import { css } from '@emotion/css';
import { useMemo } from 'react';

import { type GrafanaTheme2, dateMath } from '@grafana/data';
import { Trans, t } from '@grafana/i18n';
import {
  Alert,
  CollapsableSection,
  Divider,
  Icon,
  Link,
  LinkButton,
  LoadingPlaceholder,
  Stack,
  useStyles2,
} from '@grafana/ui';
import { useQueryParams } from 'app/core/hooks/useQueryParams';
import { alertSilencesApi } from 'app/features/alerting/unified/api/alertSilencesApi';
import { featureDiscoveryApi } from 'app/features/alerting/unified/api/featureDiscoveryApi';
import { MATCHER_ALERT_RULE_UID, SILENCES_POLL_INTERVAL_MS } from 'app/features/alerting/unified/utils/constants';
import { GRAFANA_RULES_SOURCE_NAME, getDatasourceAPIUid } from 'app/features/alerting/unified/utils/datasource';
import { type AlertmanagerAlert, type Silence, SilenceState } from 'app/plugins/datasource/alertmanager/types';

import { alertmanagerApi } from '../../api/alertmanagerApi';
import { AlertmanagerAction, useAlertmanagerAbility } from '../../hooks/useAbilities';
import { useAlertmanager } from '../../state/AlertmanagerContext';
import { parsePromQLStyleMatcherLooseSafe } from '../../utils/matchers';
import { getSilenceFiltersFromUrlParams, makeAMLink, stringifyErrorLike } from '../../utils/misc';
import { withPageErrorBoundary } from '../../withPageErrorBoundary';
import { AlertmanagerPageWrapper } from '../AlertingPageWrapper';
import { Authorize } from '../Authorize';
import { DynamicTable, type DynamicTableColumnProps, type DynamicTableItemProps } from '../DynamicTable';
import { GrafanaAlertmanagerWarning } from '../GrafanaAlertmanagerWarning';

import { Matchers } from './Matchers';
import { NoSilencesSplash } from './NoSilencesCTA';
import { SilenceDetails } from './SilenceDetails';
import { SilenceStateTag } from './SilenceStateTag';
import { SilencesFilter } from './SilencesFilter';

export interface SilenceTableItem extends Silence {
  silencedAlerts: AlertmanagerAlert[] | undefined;
}

type SilenceTableColumnProps = DynamicTableColumnProps<SilenceTableItem>;
type SilenceTableItemProps = DynamicTableItemProps<SilenceTableItem>;

const API_QUERY_OPTIONS = { pollingInterval: SILENCES_POLL_INTERVAL_MS, refetchOnFocus: true };

const SilencesTable = () => {
  const { selectedAlertmanager: alertManagerSourceName = '' } = useAlertmanager();
  const [previewAlertsSupported, previewAlertsAllowed] = useAlertmanagerAbility(
    AlertmanagerAction.PreviewSilencedInstances
  );
  const canPreview = previewAlertsSupported && previewAlertsAllowed;

  const { data: alertManagerAlerts = [], isLoading: amAlertsIsLoading } =
    alertmanagerApi.endpoints.getAlertmanagerAlerts.useQuery(
      { amSourceName: alertManagerSourceName, filter: { silenced: true, active: false, inhibited: false } },
      { ...API_QUERY_OPTIONS, skip: !canPreview }
    );

  const {
    data: silences = [],
    isLoading,
    error,
  } = alertSilencesApi.endpoints.getSilences.useQuery(
    { datasourceUid: getDatasourceAPIUid(alertManagerSourceName), ruleMetadata: true, accessControl: true },
    API_QUERY_OPTIONS
  );

  const { currentData: amFeatures } = featureDiscoveryApi.useDiscoverAmFeaturesQuery(
    { amSourceName: alertManagerSourceName ?? '' },
    { skip: !alertManagerSourceName }
  );

  const mimirLazyInitError =
    stringifyErrorLike(error).includes('the Alertmanager is not configured') && amFeatures?.lazyConfigInit;

  const styles = useStyles2(getStyles);
  const [queryParams] = useQueryParams();
  const filteredSilencesNotExpired = useFilteredSilences(silences, false);
  const filteredSilencesExpired = useFilteredSilences(silences, true);

  const { silenceState: silenceStateInParams } = getSilenceFiltersFromUrlParams(queryParams);
  const showExpiredFromUrl = silenceStateInParams === SilenceState.Expired;

  const itemsNotExpired = useMemo((): SilenceTableItemProps[] => {
    const findSilencedAlerts = (id: string) => {
      return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id));
    };
    return filteredSilencesNotExpired.map((silence) => {
      const silencedAlerts = canPreview ? findSilencedAlerts(silence.id) : undefined;
      return {
        id: silence.id,
        data: { ...silence, silencedAlerts },
      };
    });
  }, [filteredSilencesNotExpired, alertManagerAlerts, canPreview]);

  const itemsExpired = useMemo((): SilenceTableItemProps[] => {
    const findSilencedAlerts = (id: string) => {
      return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id));
    };
    return filteredSilencesExpired.map((silence) => {
      const silencedAlerts = canPreview ? findSilencedAlerts(silence.id) : undefined;
      return {
        id: silence.id,
        data: { ...silence, silencedAlerts },
      };
    });
  }, [filteredSilencesExpired, alertManagerAlerts, canPreview]);

  if (isLoading || amAlertsIsLoading) {
    return <LoadingPlaceholder text={t('alerting.silences-table.text-loading-silences', 'Loading silences...')} />;
  }

  if (mimirLazyInitError) {
    return (
      <Alert
        title={t(
          'alerting.silences-table.title-the-selected-alertmanager-has-no-configuration',
          'The selected Alertmanager has no configuration'
        )}
        severity="warning"
      >
        <Trans i18nKey="silences.table.noConfig">
          Create a new contact point to create a configuration using the default values or contact your administrator to
          set up the Alertmanager.
        </Trans>
      </Alert>
    );
  }

  if (error) {
    const errMessage = stringifyErrorLike(error) || 'Unknown error.';
    return (
      <Alert
        severity="error"
        title={t('alerting.silences-table.title-error-loading-silences', 'Error loading silences')}
      >
        {errMessage}
      </Alert>
    );
  }

  return (
    <div data-testid="silences-table">
      <GrafanaAlertmanagerWarning currentAlertmanager={alertManagerSourceName} />
      {!!silences.length && (
        <Stack direction="column">
          <SilencesFilter silences={silences} />
          <Authorize actions={[AlertmanagerAction.CreateSilence]}>
            <Stack justifyContent="end">
              <LinkButton href={makeAMLink('/alerting/silence/new', alertManagerSourceName)} icon="plus">
                <Trans i18nKey="silences.table.add-silence-button">Add Silence</Trans>
              </LinkButton>
            </Stack>
          </Authorize>
          <SilenceList
            items={itemsNotExpired}
            alertManagerSourceName={alertManagerSourceName}
            dataTestId="not-expired-table"
          />
          {itemsExpired.length > 0 && (
            <CollapsableSection
              label={t('alerting.silences-table.label-section-expired', 'Expired silences ({{numExpired}})', {
                numExpired: itemsExpired.length,
              })}
              isOpen={showExpiredFromUrl}
            >
              <div className={styles.callout}>
                <Icon className={styles.calloutIcon} name="info-circle" />
                <span>
                  <Trans i18nKey="silences.table.expired-silences">
                    Expired silences are automatically deleted after 5 days.
                  </Trans>
                </span>
              </div>
              <SilenceList
                items={itemsExpired}
                alertManagerSourceName={alertManagerSourceName}
                dataTestId="expired-table"
              />
            </CollapsableSection>
          )}
        </Stack>
      )}
      {!silences.length && <NoSilencesSplash alertManagerSourceName={alertManagerSourceName} />}
    </div>
  );
};

function SilenceList({
  items,
  alertManagerSourceName,
  dataTestId,
}: {
  items: SilenceTableItemProps[];
  alertManagerSourceName: string;
  dataTestId: string;
}) {
  const columns = useColumns(alertManagerSourceName);
  if (!!items.length) {
    return (
      <DynamicTable
        pagination={{ itemsPerPage: 25 }}
        items={items}
        cols={columns}
        isExpandable
        dataTestId={dataTestId}
        renderExpandedContent={({ data }) => {
          return (
            <>
              <Divider />
              <SilenceDetails silence={data} />
            </>
          );
        }}
      />
    );
  } else {
    return <Trans i18nKey="silences.table.no-matching-silences">No matching silences found;</Trans>;
  }
}

const useFilteredSilences = (silences: Silence[], expired = false) => {
  const [queryParams] = useQueryParams();
  return useMemo(() => {
    const { queryString } = getSilenceFiltersFromUrlParams(queryParams);
    const silenceIdsString = queryParams?.silenceIds;
    return silences.filter((silence) => {
      if (typeof silenceIdsString === 'string') {
        const idsIncluded = silenceIdsString.split(',').includes(silence.id);
        if (!idsIncluded) {
          return false;
        }
      }
      if (queryString) {
        const matchers = parsePromQLStyleMatcherLooseSafe(queryString);
        const matchersMatch = matchers.every((matcher) =>
          silence.matchers?.some(
            ({ name, value, isEqual, isRegex }) =>
              matcher.name === name &&
              matcher.value === value &&
              matcher.isEqual === isEqual &&
              matcher.isRegex === isRegex
          )
        );
        if (!matchersMatch) {
          return false;
        }
      }
      if (expired) {
        return silence.status.state === SilenceState.Expired;
      } else {
        return silence.status.state !== SilenceState.Expired;
      }
    });
  }, [queryParams, silences, expired]);
};

const getStyles = (theme: GrafanaTheme2) => ({
  callout: css({
    backgroundColor: theme.colors.background.secondary,
    borderTop: `3px solid ${theme.colors.info.border}`,
    borderRadius: theme.shape.radius.default,
    height: '62px',
    display: 'flex',
    flexDirection: 'row',
    alignItems: 'center',

    '& > *': {
      marginLeft: theme.spacing(1),
    },
  }),
  calloutIcon: css({
    color: theme.colors.info.text,
  }),
});

function useColumns(alertManagerSourceName: string) {
  const [updateSupported, updateAllowed] = useAlertmanagerAbility(AlertmanagerAction.UpdateSilence);
  const [expireSilence] = alertSilencesApi.endpoints.expireSilence.useMutation();

  const isGrafanaFlavoredAlertmanager = alertManagerSourceName === GRAFANA_RULES_SOURCE_NAME;

  return useMemo((): SilenceTableColumnProps[] => {
    const handleExpireSilenceClick = (silenceId: string) => {
      expireSilence({ datasourceUid: getDatasourceAPIUid(alertManagerSourceName), silenceId });
    };
    const columns: SilenceTableColumnProps[] = [
      {
        id: 'state',
        label: t('alerting.use-columns.columns.label.state', 'State'),
        renderCell: function renderStateTag({ data: { status } }) {
          return <SilenceStateTag state={status.state} />;
        },
        size: 3,
      },
      {
        id: 'alert-rule',
        label: t('alerting.use-columns.columns.label.alert-rule-targeted', 'Alert rule targeted'),
        renderCell: function renderAlertRuleLink({ data: { metadata } }) {
          return metadata?.rule_title ? (
            <Link
              href={`/alerting/grafana/${metadata?.rule_uid}/view?returnTo=${encodeURIComponent('/alerting/silences')}`}
            >
              {metadata.rule_title}
            </Link>
          ) : (
            'None'
          );
        },
        size: 8,
      },
      {
        id: 'matchers',
        label: t('alerting.use-columns.columns.label.matching-labels', 'Matching labels'),
        renderCell: function renderMatchers({ data: { matchers } }) {
          const filteredMatchers = matchers?.filter((matcher) => matcher.name !== MATCHER_ALERT_RULE_UID) || [];
          return <Matchers matchers={filteredMatchers} />;
        },
        size: 7,
      },
      {
        id: 'alerts',
        label: t('alerting.use-columns.columns.label.alerts-silenced', 'Alerts silenced'),
        renderCell: function renderSilencedAlerts({ data: { silencedAlerts } }) {
          return (
            <span data-testid="alerts">
              {Array.isArray(silencedAlerts)
                ? silencedAlerts.length
                : // eslint-disable-next-line @grafana/i18n/no-untranslated-strings
                  '-'}
            </span>
          );
        },
        size: 2,
      },
      {
        id: 'schedule',
        label: t('alerting.use-columns.columns.label.schedule', 'Schedule'),
        renderCell: function renderSchedule({ data: { startsAt, endsAt } }) {
          const startsAtDate = dateMath.parse(startsAt);
          const endsAtDate = dateMath.parse(endsAt);
          const dateDisplayFormat = 'YYYY-MM-DD HH:mm';
          return `${startsAtDate?.format(dateDisplayFormat)} - ${endsAtDate?.format(dateDisplayFormat)}`;
        },
        size: 7,
      },
    ];
    columns.push({
      id: 'actions',
      label: t('alerting.use-columns.label.actions', 'Actions'),
      renderCell: function renderActions({ data: silence }) {
        const isExpired = silence.status.state === SilenceState.Expired;

        const canCreate = silence?.accessControl?.create;
        const canWrite = silence?.accessControl?.write;

        const canRecreate = updateSupported && isExpired && (isGrafanaFlavoredAlertmanager ? canCreate : updateAllowed);
        const canEdit = updateSupported && !isExpired && (isGrafanaFlavoredAlertmanager ? canWrite : updateAllowed);

        return (
          <Stack gap={0.5} wrap="wrap">
            <LinkButton
              title={t('alerting.use-columns.title-view', 'View')}
              size="sm"
              variant="secondary"
              icon="eye"
              href={makeAMLink(`/alerting/silence/${silence.id}/view`, alertManagerSourceName)}
            >
              <Trans i18nKey="silences.table.view-button">View</Trans>
            </LinkButton>
            {canRecreate && (
              <LinkButton
                title={t('alerting.use-columns.title-recreate', 'Recreate')}
                size="sm"
                variant="secondary"
                icon="sync"
                href={makeAMLink(`/alerting/silence/${silence.id}/edit`, alertManagerSourceName)}
              >
                <Trans i18nKey="silences.table.recreate-button">Recreate</Trans>
              </LinkButton>
            )}
            {canEdit && (
              <>
                <LinkButton
                  title={t('alerting.use-columns.title-unsilence', 'Unsilence')}
                  size="sm"
                  variant="secondary"
                  icon="bell"
                  onClick={() => handleExpireSilenceClick(silence.id)}
                >
                  <Trans i18nKey="silences.table.unsilence-button">Unsilence</Trans>
                </LinkButton>
                <LinkButton
                  title={t('alerting.use-columns.title-edit', 'Edit')}
                  size="sm"
                  variant="secondary"
                  icon="pen"
                  href={makeAMLink(`/alerting/silence/${silence.id}/edit`, alertManagerSourceName)}
                >
                  <Trans i18nKey="silences.table.edit-button">Edit</Trans>
                </LinkButton>
              </>
            )}
          </Stack>
        );
      },
      size: 5,
    });
    return columns;
  }, [alertManagerSourceName, expireSilence, isGrafanaFlavoredAlertmanager, updateAllowed, updateSupported]);
}

function SilencesTablePage() {
  return (
    <AlertmanagerPageWrapper navId="silences" accessType="instance">
      <SilencesTable />
    </AlertmanagerPageWrapper>
  );
}

export default withPageErrorBoundary(SilencesTablePage);
