import React from 'react';
import * as Yup from 'yup';
import { TestOptionsMessage } from 'yup';
import { Formik, FormikConfig, useFormikContext } from 'formik';
import { Alert, AlertVariant, Grid, GridItem } from '@patternfly/react-core';
import { ClusterWizardStepHeader, useAlerts } from '../../../common';
import {
  AgentClusterInstallK8sResource,
  AgentK8sResource,
  ClusterDeploymentK8sResource,
} from '../../types';
import ClusterDeploymentWizardContext from './ClusterDeploymentWizardContext';
import ClusterDeploymentWizardFooter from './ClusterDeploymentWizardFooter';
import ClusterDeploymentWizardStep from './ClusterDeploymentWizardStep';
import ClusterDeploymentHostsSelection from './ClusterDeploymentHostsSelection';
import {
  ClusterDeploymentHostSelectionStepProps,
  ClusterDeploymentHostsSelectionValues,
  AgentTableActions,
} from './types';
import { hostCountValidationSchema } from './validationSchemas';
import {
  getAgentSelectorFieldsFromAnnotations,
  getIsSNOCluster,
  getWizardStepAgentStatus,
} from '../helpers';
import { canNextFromHostSelectionStep } from './wizardTransition';

const getInitialValues = ({
  agents,
  clusterDeployment,
  agentClusterInstall,
}: {
  agents: AgentK8sResource[];
  clusterDeployment: ClusterDeploymentK8sResource;
  agentClusterInstall: AgentClusterInstallK8sResource;
}): ClusterDeploymentHostsSelectionValues => {
  const isSNOCluster = getIsSNOCluster(agentClusterInstall);
  const cdName = clusterDeployment?.metadata?.name;
  const cdNamespace = clusterDeployment?.metadata?.namespace;

  let hostCount =
    (agentClusterInstall?.spec?.provisionRequirements?.controlPlaneAgents || 0) +
    (agentClusterInstall?.spec?.provisionRequirements?.workerAgents || 0);
  if (isSNOCluster) {
    hostCount = 1;
  } else if (hostCount === 2 || hostCount === 0) {
    hostCount = 3;
  } else if (hostCount === 4) {
    hostCount = 5;
  }

  const agentSelector = getAgentSelectorFieldsFromAnnotations(
    clusterDeployment?.metadata?.annotations,
  );

  const selectedIds = agents
    .filter(
      (agent) =>
        agent.spec?.clusterDeploymentName?.name === cdName &&
        agent.spec?.clusterDeploymentName?.namespace === cdNamespace,
    )
    .map((agent) => agent.metadata?.uid as string);
  const autoSelectHosts = agentSelector.autoSelect;

  return {
    autoSelectHosts,
    hostCount,
    useMastersAsWorkers: hostCount === 1 || hostCount === 3, // TODO: Recently not supported - https://issues.redhat.com/browse/MGMT-7677
    agentLabels: agentSelector?.labels || [],
    locations: agentSelector?.locations || [],
    selectedHostIds: selectedIds,
    autoSelectedHostIds: selectedIds,
  };
};

const getMinHostsCount = (selectedHostsCount: number) => (selectedHostsCount === 4 ? 5 : 3);

const getValidationSchema = (agentClusterInstall: AgentClusterInstallK8sResource) => {
  const isSNOCluster = getIsSNOCluster(agentClusterInstall);
  const getMinMessage: TestOptionsMessage<{ min: number }> = ({ min }) => {
    const message = `Please select at least ${min} hosts for the cluster`;
    if (min === 5) {
      return message + ' or select just 3 hosts instead.';
    }
    return `${message}.`;
  };

  return Yup.lazy<ClusterDeploymentHostsSelectionValues>((values) => {
    return Yup.object<ClusterDeploymentHostsSelectionValues>().shape({
      hostCount: isSNOCluster ? Yup.number() : hostCountValidationSchema,
      useMastersAsWorkers: Yup.boolean().required(),
      autoSelectedHostIds: values.autoSelectHosts
        ? Yup.array<string>().min(values.hostCount).max(values.hostCount)
        : Yup.array<string>(),
      selectedHostIds: values.autoSelectHosts
        ? Yup.array<string>()
        : isSNOCluster
        ? Yup.array<string>()
            .min(1, 'Please select one host for the cluster.')
            .max(1, 'Please select one host for the cluster.') // TODO(jtomasek): replace this with Yup.array().length() after updating Yup
        : Yup.array<string>().min(getMinHostsCount(values.selectedHostIds.length), getMinMessage),
    });
  });
};

type UseHostsSelectionFormikArgs = {
  agents: AgentK8sResource[];
  clusterDeployment: ClusterDeploymentK8sResource;
  agentClusterInstall: AgentClusterInstallK8sResource;
};

export const useHostsSelectionFormik = ({
  agents,
  clusterDeployment,
  agentClusterInstall,
}: UseHostsSelectionFormikArgs): [ClusterDeploymentHostsSelectionValues, Yup.Lazy] => {
  const initialValues = React.useMemo(
    () => getInitialValues({ agents, clusterDeployment, agentClusterInstall }),
    [agentClusterInstall, agents, clusterDeployment],
  );

  const validationSchema = React.useMemo(
    () => getValidationSchema(agentClusterInstall),
    [agentClusterInstall],
  );

  return [initialValues, validationSchema];
};

const getSelectedAgents = (
  agents: AgentK8sResource[],
  values: ClusterDeploymentHostsSelectionValues,
) => {
  const selectedHostIds = values.autoSelectHosts
    ? values.autoSelectedHostIds
    : values.selectedHostIds;
  return agents.filter((agent) => selectedHostIds.includes(agent.metadata?.uid || ''));
};

type HostSelectionFormProps = {
  agents: ClusterDeploymentHostSelectionStepProps['agents'];
  agentClusterInstall: ClusterDeploymentHostSelectionStepProps['agentClusterInstall'];
  onClose: ClusterDeploymentHostSelectionStepProps['onClose'];
  clusterDeployment: ClusterDeploymentHostSelectionStepProps['clusterDeployment'];
  aiConfigMap?: ClusterDeploymentHostSelectionStepProps['aiConfigMap'];
  onEditRole: AgentTableActions['onEditRole'];
};

const HostSelectionForm: React.FC<HostSelectionFormProps> = ({
  agents,
  agentClusterInstall,
  onClose,
  clusterDeployment,
  aiConfigMap,
  onEditRole: onEditRoleInit,
}) => {
  const { setCurrentStepId } = React.useContext(ClusterDeploymentWizardContext);
  const [showClusterErrors, setShowClusterErrors] = React.useState(false);
  const {
    values,
    isValid,
    isValidating,
    isSubmitting,
    touched,
    errors,
    validateForm,
    setTouched,
    submitForm,
    setSubmitting,
  } = useFormikContext<ClusterDeploymentHostsSelectionValues>();
  const [nextRequested, setNextRequested] = React.useState(false);
  const [showFormErrors, setShowFormErrors] = React.useState(false);
  const selectedAgents = getSelectedAgents(agents, values);

  const onEditRole = React.useCallback(
    async (agent, role) => {
      setNextRequested(false);
      setShowClusterErrors(false);
      setSubmitting(true);
      const response = await onEditRoleInit?.(agent, role);
      setSubmitting(false);
      return response;
    },
    [onEditRoleInit, setSubmitting],
  );

  const onAutoSelectChange = React.useCallback(() => {
    setNextRequested(false);
    setShowClusterErrors(false);
    setShowFormErrors(false);
  }, []);

  const onHostSelect = React.useCallback(() => {
    setNextRequested(false);
    setShowClusterErrors(false);
  }, []);

  const onNext = async () => {
    if (!showFormErrors) {
      setShowFormErrors(true);
      const errors = await validateForm();
      setTouched(
        Object.keys(errors).reduce((acc, curr) => {
          acc[curr] = true;
          return acc;
        }, {}),
      );
      if (Object.keys(errors).length) {
        return;
      }
    }
    submitForm();
    setNextRequested(true);
  };

  React.useEffect(() => {
    if (nextRequested && !isSubmitting) {
      const agentStatuses = selectedAgents.map(
        (agent) => getWizardStepAgentStatus(agent, 'hosts-selection').status.key,
      );
      if (
        agentStatuses.some((status) =>
          ['disconnected', 'disabled', 'error', 'insufficient', 'cancelled'].includes(status),
        )
      ) {
        setNextRequested(false);
      } else if (
        !!selectedAgents.length &&
        selectedAgents.every(
          (agent) => getWizardStepAgentStatus(agent, 'hosts-selection').status.key === 'known',
        )
      ) {
        setShowClusterErrors(true);
        if (canNextFromHostSelectionStep(agentClusterInstall, selectedAgents)) {
          setCurrentStepId('networking');
        }
      }
    }
  }, [nextRequested, selectedAgents, agentClusterInstall, setCurrentStepId, isSubmitting]);

  let submittingText: string | undefined = undefined;

  if (isSubmitting) {
    submittingText = 'Saving changes...';
  } else if (nextRequested && !showClusterErrors) {
    submittingText = 'Binding hosts...';
  }

  const onSyncError = React.useCallback(() => setNextRequested(false), []);

  const footer = (
    <ClusterDeploymentWizardFooter
      agentClusterInstall={agentClusterInstall}
      agents={selectedAgents}
      isSubmitting={!!submittingText}
      submittingText={submittingText}
      isNextDisabled={
        nextRequested || isSubmitting || (showFormErrors ? !isValid || isValidating : false)
      }
      onNext={onNext}
      onBack={() => setCurrentStepId('cluster-details')}
      onCancel={onClose}
      showClusterErrors={showClusterErrors}
      onSyncError={onSyncError}
    >
      {showFormErrors && errors.selectedHostIds && touched.selectedHostIds && (
        <Alert
          variant={AlertVariant.danger}
          title="Provided cluster configuration is not valid"
          isInline
        >
          {errors.selectedHostIds}
        </Alert>
      )}
    </ClusterDeploymentWizardFooter>
  );

  return (
    <ClusterDeploymentWizardStep footer={footer}>
      <Grid hasGutter>
        <GridItem>
          <ClusterWizardStepHeader>Cluster hosts</ClusterWizardStepHeader>
        </GridItem>
        <GridItem>
          <ClusterDeploymentHostsSelection
            agentClusterInstall={agentClusterInstall}
            agents={agents}
            clusterDeployment={clusterDeployment}
            aiConfigMap={aiConfigMap}
            onEditRole={onEditRole}
            onAutoSelectChange={onAutoSelectChange}
            onHostSelect={onHostSelect}
          />
        </GridItem>
      </Grid>
    </ClusterDeploymentWizardStep>
  );
};

const ClusterDeploymentHostSelectionStep: React.FC<ClusterDeploymentHostSelectionStepProps> = ({
  onSaveHostsSelection,
  ...rest
}) => {
  const { addAlert } = useAlerts();

  const { agents, clusterDeployment, agentClusterInstall } = rest;

  const [initialValues, validationSchema] = useHostsSelectionFormik({
    agents,
    clusterDeployment,
    agentClusterInstall,
  });

  const handleSubmit: FormikConfig<ClusterDeploymentHostsSelectionValues>['onSubmit'] = async (
    values,
    { setSubmitting },
  ) => {
    try {
      await onSaveHostsSelection(values);
    } catch (error) {
      addAlert({
        title: 'Failed to save host selection.',
        message: error.message as string,
      });
    } finally {
      setSubmitting(false);
    }
  };

  return (
    <Formik
      initialValues={initialValues}
      validationSchema={validationSchema}
      onSubmit={handleSubmit}
    >
      <HostSelectionForm {...rest} />
    </Formik>
  );
};

export default ClusterDeploymentHostSelectionStep;