import { createAsyncThunk } from '@reduxjs/toolkit';

import Api from '@northern.tech/store/api/general-api';

import { jqueryParam } from '../../../common/InventoryFilter/utils/jqueryParam';
import type { FilterPayload, GroupFilter, HostFilterPayload } from '../../../common/InventoryFilter';
import { notify } from '../../../common/notify';
import { CFENGINE_VERSION_ATTRIBUTE, DEFAULT_COLUMNS, OS_ATTRIBUTE } from '../../constants';
import type { Group, GroupStats, GroupType, InventoryResult } from '../../types';
import { groupBaseUrl, INVENTORY_DATA_URL, INVENTORY_STATISTIC_URL, PERSONAL_GROUPS_URL, SHARED_GROUPS_URL } from '../urls';
import { formEncoded, withApiError } from '@/utils/api';

export const fetchGroup = createAsyncThunk(
  'groupDetail/fetch',
  // No notification: the page renders both the "group not found" (404) and the
  // load-failure states from the slice itself.
  withApiError<Group, { id: string; type: GroupType }>(async ({ id, type }) => {
    const { data } = await Api.get<Group>(`${groupBaseUrl(type)}/group/${id}`);
    return { ...data, type };
  })
);

export interface InventoryQueryArgs {
  filter: FilterPayload;
  hostContextInclude: string[];
  hostFilter: HostFilterPayload | null;
  limit: number;
  sort: string;
  start: number;
}

// Fetch the filtered hosts table. The group context never sets host
// contexts/excludes itself, so those stay empty.
export const runInventoryQuery = createAsyncThunk(
  'groupDetail/runQuery',
  withApiError<InventoryResult, InventoryQueryArgs>(async args => {
    const { data } = await Api.post<InventoryResult>(INVENTORY_DATA_URL, {
      sort: args.sort,
      select: DEFAULT_COLUMNS,
      filter: args.filter,
      hostFilter: args.hostFilter,
      limit: args.limit,
      start: args.start,
      showSQL: true,
      hostContextInclude: args.hostContextInclude,
      hostContextExclude: []
    });
    return data;
  }, 'Error encountered. Please view server logs for more detailed information.')
);

// One statistic call per charted attribute; returns a value->count map.
const fetchStatistic = async (attributeName: string, filters: GroupFilter | undefined): Promise<Record<string, number>> => {
  const { data } = await Api.post<Record<string, number>>(INVENTORY_STATISTIC_URL, jqueryParam({ type: 'pie', attributeName, filters }), formEncoded);
  return data ?? {};
};

export const fetchStats = createAsyncThunk(
  'groupDetail/stats',
  withApiError<GroupStats, GroupFilter | undefined>(async filters => {
    const [osDistribution, versions] = await Promise.all([fetchStatistic(OS_ATTRIBUTE, filters), fetchStatistic(CFENGINE_VERSION_ATTRIBUTE, filters)]);
    return {
      os: Object.keys(osDistribution),
      versions: Object.keys(versions),
      osDistribution
    };
  }, 'Failed to load group statistics.')
);

export const saveGroupFilter = createAsyncThunk(
  'groupDetail/saveFilter',
  withApiError<boolean, { groupFilter: Pick<GroupFilter, 'filter' | 'hostFilter' | 'hostContextInclude'>; id: string; type: GroupType }>(
    async ({ id, type, groupFilter }, { dispatch }) => {
      await Api.put(`${groupBaseUrl(type)}/group/${id}`, { filter: groupFilter });
      notify.success('Changes saved.');
      dispatch(fetchGroup({ id, type }));
      return true;
    },
    'Failed to save changes.'
  )
);

export const saveGroupTitle = createAsyncThunk(
  'groupDetail/saveTitle',
  withApiError<string, { id: string; name: string; type: GroupType }>(async ({ id, type, name }) => {
    await Api.put(`${groupBaseUrl(type)}/group/${id}`, { name });
    return name;
  }, 'Failed to rename group.')
);

export const makeShared = createAsyncThunk(
  'groupDetail/makeShared',
  withApiError<string | number, string>(async id => {
    const { data } = await Api.post<{ id: string | number }>(`${PERSONAL_GROUPS_URL}/shared/${id}`);
    notify.success('Group shared.');
    return data.id;
  }, 'Failed to share group.')
);

export const makePersonal = createAsyncThunk(
  'groupDetail/makePersonal',
  withApiError<string | number, string>(async id => {
    const { data } = await Api.post<{ id: string | number }>(`${SHARED_GROUPS_URL}/makePersonal/${id}`);
    notify.success('Group made personal.');
    return data.id;
  }, 'Failed to make group personal.')
);
