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

import { notify } from '../../../common/notify';
import type { ProjectFormValues } from '../../types';
import * as api from '../api';
import { withApiError } from '@/utils/api';

// The write API takes camelCase keys and returns snake_case on read (matching
// the legacy AngularJS behavior). Password / private key are only sent when the
// user actually entered a value; otherwise null keeps the stored secret.
const toPayload = (values: ProjectFormValues) => ({
  name: values.name,
  branch: values.branch,
  username: values.username,
  repositoryUrl: values.repositoryUrl,
  authenticationType: values.authenticationType,
  classicPolicySet: values.classicPolicySet,
  sshKeyId: values.sshKeyId ?? null,
  password: values.password ? values.password : null,
  sshPrivateKey: values.sshPrivateKey ? values.sshPrivateKey : null
});

export const fetchProjectsList = createAsyncThunk('build/fetchProjectsList', (limit: number = 50) =>
  api.listProjects(limit)
);

export const fetchProject = createAsyncThunk('build/fetchProject', (id: number | string) => api.getProject(id));

// Composite load used by every page that shows the aside + deploy header:
// project, its modules, VCS sync status, hub key, and the deployed flag.
export const loadProjectInfo = createAsyncThunk(
  'build/loadProjectInfo',
  async (id: number | string, { dispatch }) => {
    dispatch(fetchAnyDeployed());
    dispatch(fetchHubKey());
    dispatch(fetchProjectsList(50));
    await dispatch(fetchProject(id));
    dispatch(fetchVcsSync(id));
    dispatch(fetchProjectModules(id));
  }
);

export const fetchProjectModules = createAsyncThunk('build/fetchProjectModules', (id: number | string) =>
  api.getProjectModules(id)
);

export const fetchVcsSync = createAsyncThunk('build/fetchVcsSync', (id: number | string) => api.vcsSynchronized(id));

export const fetchAnyDeployed = createAsyncThunk('build/fetchAnyDeployed', async () => {
  const { deployed } = await api.anyProjectDeployed();
  return deployed;
});

export const fetchHubKey = createAsyncThunk('build/fetchHubKey', async () => {
  const info = await api.getHubInfo();
  return info.hostkey ?? null;
});

export const createProject = createAsyncThunk(
  'build/createProject',
  withApiError((values: ProjectFormValues) => api.createProject(toPayload(values)))
);

export const createLocalProject = createAsyncThunk(
  'build/createLocalProject',
  withApiError((masterfiles: string) => api.createLocalProject(masterfiles))
);

export const updateProject = createAsyncThunk(
  'build/updateProject',
  withApiError(({ id, values }: { id: number | string; values: ProjectFormValues }) =>
    api.updateProject(id, toPayload(values))
  )
);

export const deleteProject = createAsyncThunk(
  'build/deleteProject',
  withApiError(async ({ id, stopDeploying }: { id: number | string; stopDeploying: boolean }) => {
    if (stopDeploying) {
      const info = await api.getHubInfo();
      if (info.hostkey) {
        const item = (await api.getCmdbItem(info.hostkey)) as { entry_id?: string } | undefined;
        const entryId = item?.entry_id;
        if (entryId) await api.deleteCmdbItem(entryId);
      }
      await api.clearVcs();
    }
    await api.deleteProject(id);
    notify.success('Project successfully deleted.');
    return id;
  })
);

// ===== Deployment =====

const getHubKeyOrThrow = async (): Promise<string> => {
  const info = await api.getHubInfo();
  const hubKey = info.hostkey ?? null;
  if (!hubKey) {
    throw {
      response: {
        data: 'Cannot get the Hub key to write needed CMDB data for deployment. Please ensure that Hub is reported and re-bootstrap if not.'
      }
    };
  }
  return hubKey;
};

// Mirrors projectsService.deploy: sync VCS settings, then ensure the CMDB
// update-policy item exists (creating it + an agent run when missing), then run
// the agent to pull the staged policy.
const runDeploy = async (id: number | string, localDeploy = false) => {
  await api.synchronizeVcs(id, localDeploy);
  const hubKey = await getHubKeyOrThrow();
  const cmdbItem = await api.getCmdbItem(hubKey);
  if (!cmdbItem) {
    await api.createCmdbItem(hubKey, api.CMDB_UPDATE_POLICY);
    await api.agentRun(hubKey);
  }
  await api.agentRun(hubKey);
};

export const pushProject = createAsyncThunk(
  'build/pushProject',
  withApiError((id: number | string) => api.pushProject(id))
);

export const deployProject = createAsyncThunk(
  'build/deployProject',
  withApiError((id: number | string) => runDeploy(id, false))
);

export const pushAndDeployProject = createAsyncThunk(
  'build/pushAndDeployProject',
  withApiError(async (id: number | string) => {
    await api.pushProject(id);
    await runDeploy(id, false);
  })
);

export const localDeployProject = createAsyncThunk(
  'build/localDeployProject',
  withApiError(async (id: number | string) => {
    await api.localDeployProject(id);
    await runDeploy(id, true);
  })
);

export const refreshProjectStatus = createAsyncThunk(
  'build/refreshProjectStatus',
  withApiError(async (id: number | string) => {
    const { status } = await api.refreshProject(id);
    return status;
  })
);

export const rebaseProject = createAsyncThunk(
  'build/rebaseProject',
  withApiError((id: number | string) => api.rebaseProject(id))
);
export const forcePullProject = createAsyncThunk(
  'build/forcePullProject',
  withApiError((id: number | string) => api.forcePullProject(id))
);
export const forceRebaseProject = createAsyncThunk(
  'build/forceRebaseProject',
  withApiError((id: number | string) => api.forceRebaseProject(id))
);

export const setDefaultAction = createAsyncThunk(
  'build/setDefaultAction',
  withApiError(({ id, action }: { action: string; id: number | string }) => api.setDefaultAction(id, action))
);

export const addModuleToProject = createAsyncThunk(
  'build/addModuleToProject',
  withApiError(({ name, projectId, url, version }: { name?: string; projectId: number | string; url?: string; version?: string }) =>
    url ? api.addModuleByUrl(projectId, url) : api.addModule(projectId, name as string, version as string)
  )
);

export const removeModuleFromProject = createAsyncThunk(
  'build/removeModuleFromProject',
  withApiError(({ encodedName, name, projectId }: { encodedName?: boolean; name: string; projectId: number | string }) =>
    api.removeProjectModule(projectId, name, encodedName)
  )
);

export const updateModuleVersion = createAsyncThunk(
  'build/updateModuleVersion',
  withApiError(
    ({
      gitMessage,
      name,
      projectId,
      version
    }: {
      gitMessage?: string;
      name: string;
      projectId: number | string;
      version: string;
    }) => api.updateModuleVersion(projectId, name, version, gitMessage)
  )
);

// ===== SSH key pair =====

export const generateSshKey = createAsyncThunk(
  'build/generateSshKey',
  withApiError((_: void) => api.generateSshKey())
);
export const fetchSshKey = createAsyncThunk('build/fetchSshKey', (id: number | string) => api.getSshKey(id));
export const deleteSshKey = createAsyncThunk(
  'build/deleteSshKey',
  withApiError(async (id: number | string) => {
    await api.deleteSshKey(id);
    return id;
  })
);
