/**
 * Copyright IBM Corp. 2016, 2025
 * SPDX-License-Identifier: BUSL-1.1
 */

import { isSameMonthUTC, parseAPITimestamp } from 'core/utils/date-formatters';
import { isWithinInterval } from 'date-fns';
import { ROOT_NAMESPACE } from 'vault/services/namespace';

import type { VersionHistory } from 'vault/client-counts';
import type {
  ActivityExportData,
  ByNamespaceClients,
  ClientFilterTypes,
  MountClients,
} from 'vault/vault/client-counts/activity-api';

/*
The client count utils define consts and methods, such as filtering, related to client count data.
*/

// Add new sys/activity/counters client count types here
export const CLIENT_TYPES = [
  'acme_clients',
  'clients', // summation of total clients
  'entity_clients',
  'non_entity_clients',
  'secret_syncs',
] as const;

// map to dropdowns for filtering client count tables
export enum ClientFilters {
  NAMESPACE = 'namespace_path',
  MOUNT_PATH = 'mount_path',
  MOUNT_TYPE = 'mount_type',
  // this filter/query param does not map to a key in either API response and is handled ~special~
  MONTH = 'month',
}

// client_type in the exported activity data differs slightly from the types of client keys
// returned by sys/internal/counters/activity endpoint (:
export const EXPORT_CLIENT_TYPES = ['non-entity-token', 'pki-acme', 'secret-sync', 'entity'] as const;

// returns array of VersionHistoryModels for noteworthy upgrades: 1.9, 1.10
// that occurred between timestamps (i.e. queried activity data)
export const filterVersionHistory = (versionHistory: VersionHistory[], start?: Date, end?: Date) => {
  if (versionHistory && start && end) {
    const upgrades = versionHistory.reduce((array: VersionHistory[], upgradeData) => {
      const isRelevantHistory = (v: string) => {
        return (
          upgradeData.version.match(v) &&
          // only add if there is a previous version, otherwise this upgrade is the users' first version
          upgradeData.previous_version &&
          // only add first match, disregard subsequent patch releases of the same version
          !array.some((d: VersionHistory) => d.version.match(v))
        );
      };

      ['1.9', '1.10', '1.17'].forEach((v) => {
        if (isRelevantHistory(v)) array.push(upgradeData);
      });

      return array;
    }, []);

    // if there are noteworthy upgrades, only return those during queried date range
    if (upgrades.length) {
      return upgrades.filter(({ timestamp_installed }) => {
        const upgradeDate = parseAPITimestamp(timestamp_installed) as Date;
        return isWithinInterval(upgradeDate, { start, end });
      });
    }
  }
  return [];
};

// *Performance note*
// The client dashboard renders dropdown lists that specify filters. When the user selects a dropdown item (filter)
// it updates the query param and this method is called to filter the data passed to the displayed table.
// This method is not doing anything computationally expensive so it should be fine for filtering up to 50K rows of data.
// If activity data (either the by_namespace list or rows of data in the activity export API) grow past that, then we
// will want to look at converting this to a restartable task or do something else :)
export function filterTableData(
  data: MountClients[] | ActivityExportData[],
  filters: Record<ClientFilterTypes, string>
): MountClients[] | ActivityExportData[] {
  // Return original data if no filters are specified
  if (!filters || Object.values(filters).every((v) => !v)) {
    return data;
  }

  return data.filter((datum) => {
    // Datum must satisfy every filter
    return Object.entries(filters).every(([filterKey, filterValue]) => {
      // If no filter is specified for that key, return true
      if (!filterValue) return true;
      // Otherwise only return true if the datum matches the filter
      return matchesFilter(datum, filterKey as ClientFilterTypes, filterValue);
    });
  }) as typeof data;
}

const matchesFilter = (
  datum: ActivityExportData | MountClients,
  filterKey: ClientFilterTypes,
  filterValue: string
) => {
  // Only ActivityExportData data is ever filtered by 'client_first_used_time' (not MountClients)
  if (filterKey === ClientFilters.MONTH) {
    return 'client_first_used_time' in datum
      ? isSameMonthUTC(datum.client_first_used_time, filterValue)
      : false;
  }

  const datumValue = datum[filterKey];
  // The API returns and empty string as the namespace_path for the "root" namespace.
  // When a user selects "root" as a namespace filter we need to match the datum value
  // as either an empty string (for the activity export data) OR as "root"
  // (the by_namespace data is serialized to make "root" the namespace_path).
  if (filterKey === ClientFilters.NAMESPACE && filterValue === 'root') {
    return datumValue === ROOT_NAMESPACE || datumValue === filterValue;
  }
  return datumValue === filterValue;
};

export const flattenMounts = (namespaceArray: ByNamespaceClients[]) =>
  namespaceArray.map((n) => n.mounts).flat();

export const filterIsSupported = (f: string): f is ClientFilterTypes =>
  Object.values(ClientFilters).includes(f as ClientFilterTypes);
