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

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

import { notify } from '../../../common/notify';
import type { Group, GroupType, ReferencingRule, SortState } from '../../types';
import { groupBaseUrl, SHARED_GROUPS_URL } from '../urls';
import { withApiError } from '@/utils/api';

interface GroupsListResponse {
  data: Group[];
  meta: { total: number };
}

interface GroupsListPage {
  items: Group[];
  total: number;
}

export interface FetchGroupsArgs {
  page: number;
  rowsPerPage: number;
  sort: SortState;
  type: GroupType;
}

export const fetchGroups = createAsyncThunk(
  'groupsList/fetch',
  withApiError<GroupsListPage, FetchGroupsArgs>(async ({ type, page, rowsPerPage, sort }) => {
    const { data } = await Api.get<GroupsListResponse>(`${groupBaseUrl(type)}/list`, {
      params: {
        skip: (page - 1) * rowsPerPage,
        limit: rowsPerPage,
        sortColumn: sort.column,
        sortDescending: sort.descending ? 'true' : 'false',
        includeHostsCount: 'yes'
      }
    });
    return { items: data.data, total: data.meta.total };
  })
);

export const addNewGroup = createAsyncThunk(
  'groupsList/add',
  withApiError<{ id: string | number; type: GroupType }, GroupType>(async type => {
    const { data } = await Api.post<{ id: string | number }>(`${groupBaseUrl(type)}/new_group`, []);
    return { id: data.id, type };
  }, 'Failed to create group.')
);

export const removeGroup = createAsyncThunk(
  'groupsList/remove',
  withApiError<string | number, { id: string | number; type: GroupType }>(async ({ id, type }) => {
    await Api.delete(`${groupBaseUrl(type)}/group/${id}`);
    notify.success('Group successfully removed.');
    return id;
  }, 'Failed to remove group.')
);

// Shared groups can be referenced by compliance rules; warn before deleting.
export const checkReferences = createAsyncThunk(
  'groupsList/checkReferences',
  withApiError<ReferencingRule[], string | number>(async id => {
    const { data } = await Api.get<{ rules: ReferencingRule[] }>(`${SHARED_GROUPS_URL}/check_references/${id}`);
    return data.rules || [];
  })
);

export const changePriority = createAsyncThunk(
  'groupsList/changePriority',
  withApiError<{ id: string | number; priority: number }, { id: string | number; priority: number }>(async ({ id, priority }) => {
    await Api.put(`${SHARED_GROUPS_URL}/group/${id}`, { priority });
    notify.success('Group\'s priority changed.');
    return { id, priority };
  }, 'Failed to change priority.')
);

export const toggleFavorite = createAsyncThunk(
  'groupsList/favorite',
  withApiError<{ favorite: boolean; id: string | number }, { favorite: boolean; id: string | number; type: GroupType }>(async ({ id, type, favorite }) => {
    const url = `${groupBaseUrl(type)}/favorite/${id}`;
    if (favorite) {
      await Api.post(url);
    } else {
      await Api.delete(url);
    }
    return { id, favorite };
  }, 'Failed to update favorites.')
);

// Highest numeric suffix among the same-named groups, so duplicating
// "web duplicate 9" yields "web duplicate 10" instead of colliding with it.
const nextDuplicateNumber = (groups: Group[]): number => Math.max(0, ...groups.map(group => Number(group.name.match(/\d+$/)?.[0] ?? 0))) + 1;

// Duplicate a group into the chosen target type. Shared targets get a trailing
// number when a same-named group already exists.
export const duplicateGroup = createAsyncThunk(
  'groupsList/duplicate',
  withApiError<{ id: string | number; type: GroupType }, { sourceId: string | number; sourceType: GroupType; targetType: GroupType }>(
    async ({ sourceId, sourceType, targetType }) => {
      const { data: group } = await Api.get<Group>(`${groupBaseUrl(sourceType)}/group/${sourceId}`);
      group.name += ' duplicate';

      if (targetType === 'shared') {
        const { data: existing } = await Api.get<{ data: Group[] }>(`${SHARED_GROUPS_URL}/list`, {
          params: { searchQuery: group.name, sortColumn: 'name', sortDescending: 'true', limit: 20 }
        });
        if (existing.data.length > 0) {
          group.name += ` ${nextDuplicateNumber(existing.data)}`;
        }
      }

      const { data: created } = await Api.post<{ id: string | number }>(`${groupBaseUrl(targetType)}/group`, group);
      return { id: created.id, type: targetType };
    },
    'Failed to duplicate group.'
  )
);
