import { useMemo, useState } from 'react';

import { Trans } from '@grafana/i18n';
import { reportInteraction } from '@grafana/runtime';
import { Button, Stack } from '@grafana/ui';
import { appEvents } from 'app/core/app_events';
import { buildNotificationButton } from 'app/core/components/AppNotifications/NotificationButton';
import { createSuccessNotification } from 'app/core/copy/appNotification';
import { notifyApp } from 'app/core/reducers/appNotification';
import { AnnoKeyFolder } from 'app/features/apiserver/types';
import { getDashboardAPI } from 'app/features/dashboard/api/dashboard_api';
import { isRootFolderUID } from 'app/features/search/constants';
import { useDispatch } from 'app/types/store';

import { deletedDashboardsCache } from '../../search/service/deletedDashboardsCache';
import { useRestoreDashboardMutation } from '../api/browseDashboardsAPI';
import { useRecentlyDeletedStateManager } from '../api/useRecentlyDeletedStateManager';
import { useActionSelectionState } from '../state/hooks';
import { clearFolders, setAllSelection } from '../state/slice';
import { getRestoreNotificationData } from '../utils/notifications';

import { RestoreModal } from './RestoreModal';

export function RecentlyDeletedActions() {
  const dispatch = useDispatch();
  const selectedItemsState = useActionSelectionState();
  const [searchState, stateManager] = useRecentlyDeletedStateManager();
  const [restoreDashboard] = useRestoreDashboardMutation();
  const [isBulkRestoreLoading, setIsBulkRestoreLoading] = useState(false);
  const [isRestoreModalOpen, setIsRestoreModalOpen] = useState(false);

  const selectedDashboards = useMemo(() => {
    return Object.entries(selectedItemsState.dashboard)
      .filter(([_, selected]) => selected)
      .map(([uid]) => uid);
  }, [selectedItemsState.dashboard]);

  const selectedDashboardOrigin = useMemo(() => {
    if (!searchState.result || selectedDashboards.length === 0) {
      return undefined;
    }

    let originCandidate: string | undefined;
    for (const selectedDashboard of selectedDashboards) {
      const index = searchState.result.view.fields.uid.values.findIndex((e) => e === selectedDashboard);
      if (index === -1) {
        return undefined;
      }

      // Searcher reports root-parented items with the "general" UID, but the
      // restore API doesn't accept it — convert back to "" so the dashboard
      // is restored to the root.
      const location = searchState.result.view.fields.location.values[index];
      const fixedLocation = isRootFolderUID(location) ? '' : location;

      if (originCandidate === undefined) {
        originCandidate = fixedLocation;
        continue;
      }

      if (originCandidate !== fixedLocation) {
        return undefined;
      }
    }

    return originCandidate;
  }, [selectedDashboards, searchState.result]);

  const getErrorMessage = (error: unknown) => {
    if (error instanceof Error) {
      return error.message;
    }
    if (typeof error === 'string') {
      return error;
    }
    if (error && typeof error === 'object' && 'message' in error) {
      return String(error.message);
    }
    if (error) {
      return JSON.stringify(error);
    }
    return '';
  };

  const onRestore = async (restoreTarget: string) => {
    const resultsView = stateManager.state.result?.view.toArray();
    if (!resultsView) {
      return;
    }

    setIsBulkRestoreLoading(true);

    const promises = selectedDashboards.map(async (uid) => {
      const table = await deletedDashboardsCache.getAsTable();
      const row = table.rows.find((r) => r.object.metadata.name === uid);
      if (!row) {
        console.warn(`Dashboard ${uid} not found in deleted items`);
        return { uid, error: 'not_found' };
      }

      const deleteRV = row.object.metadata.resourceVersion;
      if (!deleteRV) {
        console.warn(`Dashboard ${uid} is missing a resourceVersion in the trash listing`);
        return { uid, error: 'not_found' };
      }
      // The RV on a trash row is the delete event's RV, which points at the
      // tombstone (and on some storage backends returns 404). Step back by one
      // so the read resolves to the dashboard as it was just before delete.
      const previousRV = (BigInt(deleteRV) - BigInt(1)).toString();

      const api = await getDashboardAPI();
      const dashboard = await api.getDashboard(uid, { resourceVersion: previousRV });

      const copy = structuredClone(dashboard);
      copy.metadata = {
        ...copy.metadata,
        annotations: { ...copy.metadata?.annotations, [AnnoKeyFolder]: restoreTarget },
      };

      return restoreDashboard({ dashboard: copy });
    });

    const results = await Promise.allSettled(promises);

    // Separate successful and failed restores
    const successful: string[] = [];
    const failed: Array<{ uid: string; error: string }> = [];

    results.forEach((result, index) => {
      const dashboardUid = selectedDashboards[index];
      if (result.status === 'rejected') {
        const errorMessage = getErrorMessage(result.reason);
        if (errorMessage) {
          failed.push({ uid: dashboardUid, error: errorMessage });
        }
      } else if (result.value.error) {
        const errorMessage = getErrorMessage(result.value.error);
        if (errorMessage) {
          failed.push({ uid: dashboardUid, error: errorMessage });
        }
      } else if ('data' in result.value && result.value.data?.name) {
        // Track the UID of successfully restored dashboards
        successful.push(dashboardUid);
      }
    });

    const parentUIDs = new Set<string | undefined>();
    for (const uid of selectedDashboards) {
      const foundItem = resultsView.find((v) => v.uid === uid);
      if (!foundItem) {
        continue;
      }
      // Search API reports root-parented items with the "general" UID —
      // convert that back to undefined.
      const folderUID = isRootFolderUID(foundItem.location) ? undefined : foundItem.location;
      parentUIDs.add(folderUID);
    }
    dispatch(clearFolders(Array.from(parentUIDs)));
    dispatch(setAllSelection({ isSelected: false, folderUID: undefined }));

    deletedDashboardsCache.removeItems(successful);
    await stateManager.doSearch();

    const notificationData = getRestoreNotificationData(successful, failed, restoreTarget);
    if (notificationData) {
      if (notificationData.kind === 'action') {
        const component = buildNotificationButton({
          title: notificationData.data.title,
          buttonLabel: notificationData.data.buttonLabel,
          href: notificationData.data.targetUrl,
        });
        dispatch(notifyApp(createSuccessNotification('', '', undefined, component)));
      } else {
        appEvents.publish({
          type: notificationData.data.alertType,
          payload: [notificationData.data.message],
        });
      }
    }
    setIsBulkRestoreLoading(false);
    setIsRestoreModalOpen(false);
  };

  const showRestoreModal = () => {
    reportInteraction('grafana_restore_clicked', {
      item_counts: {
        dashboard: selectedDashboards.length,
      },
    });
    setIsRestoreModalOpen(true);
  };

  return (
    <>
      <Stack gap={1}>
        <Button onClick={showRestoreModal} variant="secondary">
          <Trans i18nKey="recently-deleted.buttons.restore">Restore</Trans>
        </Button>
      </Stack>
      {isRestoreModalOpen && (
        <RestoreModal
          onConfirm={onRestore}
          onDismiss={() => setIsRestoreModalOpen(false)}
          selectedDashboards={selectedDashboards}
          originCandidate={selectedDashboardOrigin}
          isLoading={isBulkRestoreLoading}
        />
      )}
    </>
  );
}
