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

import Api, { formEncoded } from '@/api';

import type { NodeDescription, Profile, TreeHost } from '../../types';

// Tree state shared with selectors via the host store. We type the thunk
// `getState` results loosely (`unknown`) here to avoid a circular import with
// the slice — selectors and component code consume strongly-typed state.

export interface TreeMetadata {
  isAdmin: boolean;
  roles: string[];
}

export interface ProfileSettings {
  name: string;
  sharedPermission: string[];
  globalTree: boolean;
}

interface ClassesPage {
  items: string[];
  page: number;
  filter?: string;
}

interface NodeHostsPage {
  key: string;
  hosts: TreeHost[];
  page: number;
  hasMore: boolean;
}

interface CountBatchResult {
  pathKey: string;
  count: number;
}

const toFormBody = (data: Record<string, string>): string =>
  new URLSearchParams(data).toString();

export const fetchProfiles = createAsyncThunk('hostsTree/fetchProfiles', async () => {
  const { data } = await Api.get<Profile[]>('/astrolabe/profile');
  return data;
});

export const fetchTreeMetadata = createAsyncThunk('hostsTree/fetchTreeMetadata', async () => {
  const { data } = await Api.get<TreeMetadata>('/astrolabe/metadata');
  return data;
});

interface FetchProfileDataArgs {
  profileId: number | string;
  sharedTree?: boolean;
  sharedFrom?: string;
}

export const fetchProfileData = createAsyncThunk(
  'hostsTree/fetchProfileData',
  async ({ profileId, sharedTree = false, sharedFrom }: FetchProfileDataArgs) => {
    const sharedSegment = sharedTree ? '/1' : '/0';
    const fromSegment = sharedFrom ? `/${sharedFrom}` : '';
    // legacy cache-buster — the endpoint returns 304s otherwise on quick edits
    const url = `/astrolabe/profile/${profileId}${sharedSegment}${fromSegment}/${Math.random()}`;
    const { data } = await Api.get<NodeDescription[]>(url);
    return data;
  }
);

interface CreateProfileArgs {
  name: string;
  nodeDescription?: unknown[];
  sharedPermission?: string[];
  global?: boolean;
}

export const createProfile = createAsyncThunk(
  'hostsTree/createProfile',
  async ({ name, nodeDescription = [], sharedPermission = [], global = false }: CreateProfileArgs) => {
    const { data } = await Api.put<{ id: number | string }>(
      `/astrolabe/profile/${encodeURIComponent(name)}`,
      JSON.stringify({ nodeDescription, sharedPermission, global }),
      { headers: { 'Content-Type': 'application/json' } }
    );
    return data;
  }
);

export const deleteProfile = createAsyncThunk(
  'hostsTree/deleteProfile',
  async (id: number | string) => {
    await Api.delete(`/astrolabe/profile/${id}`);
    return id;
  }
);

interface UpdateProfileSettingsArgs {
  id: number | string;
  newname: string;
  sharedPermission: string[];
  globalTree: boolean;
}

export const updateProfileSettings = createAsyncThunk(
  'hostsTree/updateProfileSettings',
  async ({ id, newname, sharedPermission, globalTree }: UpdateProfileSettingsArgs) => {
    const body = new URLSearchParams();
    body.set('newname', newname);
    body.set('globalTree', globalTree ? '1' : '0');
    sharedPermission.forEach((role, i) => body.append(`sharedPermission[${i}]`, role));
    await Api.post(`/astrolabe/profile/${id}`, body.toString(), formEncoded);
    return { id, newname, sharedPermission, globalTree };
  }
);

interface UpdateProfileDataArgs {
  profileId: number | string;
  nodeData: unknown;
}

export const updateProfileData = createAsyncThunk(
  'hostsTree/updateProfileData',
  async ({ profileId, nodeData }: UpdateProfileDataArgs) => {
    await Api.post(
      `/astrolabe/profile/${profileId}`,
      toFormBody({ nodeData: JSON.stringify(nodeData) }),
      formEncoded
    );
    return { profileId };
  }
);

export const fetchProfileSettings = createAsyncThunk(
  'hostsTree/fetchProfileSettings',
  async (id: number | string) => {
    const { data } = await Api.get<ProfileSettings>(`/astrolabe/profileSettings/${id}`);
    return { id, settings: data };
  }
);

export const fetchSubscribable = createAsyncThunk('hostsTree/fetchSubscribable', async () => {
  const { data } = await Api.get<Profile[]>('/astrolabe/subscribable');
  return data;
});

export const subscribeProfile = createAsyncThunk(
  'hostsTree/subscribeProfile',
  async (id: number | string) => {
    await Api.put(`/astrolabe/subscribe/${id}`);
    return id;
  }
);

export const unsubscribeProfile = createAsyncThunk(
  'hostsTree/unsubscribeProfile',
  async (id: number | string) => {
    await Api.delete(`/astrolabe/subscribe/${id}`);
    return id;
  }
);

interface FetchClassesArgs {
  page?: number;
  filter?: string;
  // when true, replace the existing items; otherwise append
  replace?: boolean;
}

export const fetchClasses = createAsyncThunk(
  'hostsTree/fetchClasses',
  async ({ page = 1, filter }: FetchClassesArgs): Promise<ClassesPage> => {
    const pathFilter = filter ? `/${filter}` : '';
    const { data } = await Api.post<unknown[]>(`/widget/allclasses/${page}${pathFilter}`, '', formEncoded);
    const items = Array.isArray(data)
      ? data.map(row => (Array.isArray(row) ? String(row[0] ?? '') : String(row)))
      : [];
    return { items, page, filter };
  }
);

interface FetchHostCountArgs {
  includes: string[];
  excludes: string[];
  pathKey: string;
}

export const fetchHostCount = createAsyncThunk(
  'hostsTree/fetchHostCount',
  async ({ includes, excludes, pathKey }: FetchHostCountArgs) => {
    const params = new URLSearchParams();
    if (includes.length > 0) params.set('includes', includes.join(','));
    if (excludes.length > 0) params.set('excludes', excludes.join('|'));
    const { data } = await Api.get<number | string>(`/host/count?${params.toString()}`);
    const count = typeof data === 'number' ? data : parseInt(String(data), 10) || 0;
    return { pathKey, count };
  }
);

interface BatchEntry {
  id: string;
  includes: string;
  excludes: string;
  pathKey: string;
}

export const fetchHostCountBatch = createAsyncThunk(
  'hostsTree/fetchHostCountBatch',
  async (queries: BatchEntry[]): Promise<CountBatchResult[]> => {
    if (queries.length === 0) return [];
    // PHP $_POST bracket-encoding: data[0][N][id]=...&data[0][N][includes]=... etc.
    const body = new URLSearchParams();
    queries.forEach((q, i) => {
      body.append(`data[0][${i}][id]`, q.id);
      body.append(`data[0][${i}][includes]`, q.includes);
      body.append(`data[0][${i}][excludes]`, q.excludes);
    });
    const { data } = await Api.post<{ id: string; count: number }[]>(
      '/host/updateCount',
      body.toString(),
      formEncoded
    );
    const idToPath = new Map(queries.map(q => [q.id, q.pathKey]));
    return data.map(r => ({ pathKey: idToPath.get(r.id) ?? r.id, count: r.count }));
  }
);

interface FetchNodeHostsArgs {
  key: string;
  includes: string[];
  excludes: string[];
  page: number;
  perPage?: number;
}

export const fetchNodeHosts = createAsyncThunk(
  'hostsTree/fetchNodeHosts',
  async ({ key, includes, excludes, page, perPage = 50 }: FetchNodeHostsArgs): Promise<NodeHostsPage> => {
    const body = new URLSearchParams();
    body.set('page', String(page));
    body.set('count', String(perPage));
    if (includes.length > 0) body.set('includes', includes.join(','));
    if (excludes.length > 0) body.set('excludes', excludes.join('|'));
    const { data } = await Api.post<TreeHost[] | unknown>('/astrolabe/host', body.toString(), formEncoded);
    const hosts = Array.isArray(data) ? (data as TreeHost[]) : [];
    return { key, hosts, page, hasMore: hosts.length === perPage };
  }
);
