// Copyright 2019 Northern.tech AS
//
//    Licensed under the Apache License, Version 2.0 (the "License");
//    you may not use this file except in compliance with the License.
//    You may obtain a copy of the License at
//
//        http://www.apache.org/licenses/LICENSE-2.0
//
//    Unless required by applicable law or agreed to in writing, software
//    distributed under the License is distributed on an "AS IS" BASIS,
//    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//    See the License for the specific language governing permissions and
//    limitations under the License.
import { useEffect, useRef, useState } from 'react';
import { FormProvider, useForm } from 'react-hook-form';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router';

import { ExpandMore } from '@mui/icons-material';
import {
  Accordion,
  AccordionDetails,
  AccordionSummary,
  Button,
  FormGroup,
  Typography,
  accordionClasses,
  accordionSummaryClasses,
  lighten
} from '@mui/material';
import { makeStyles } from 'tss-react/mui';

import BaseDrawer from '@northern.tech/common-ui/BaseDrawer';
import Confirm from '@northern.tech/common-ui/Confirm';
import { FormCheckbox } from '@northern.tech/common-ui/forms/FormCheckbox';
import { ALL_DEVICES, onboardingSteps } from '@northern.tech/store/constants';
import {
  getDeviceCountsByStatus,
  getDevicesById,
  getFeatures,
  getGlobalSettings,
  getGroupData,
  getGroupNames,
  getIdAttribute,
  getIsEnterprise,
  getOnboardingState,
  getReleaseListState,
  getReleasesById,
  getTenantCapabilities,
  getUserCapabilities
} from '@northern.tech/store/selectors';
import { advanceOnboarding, createDeployment, getDeploymentsConfig, getRelease, getReleases } from '@northern.tech/store/thunks';
import { isEmpty, toggle } from '@northern.tech/utils/helpers';
import pluralize from 'pluralize';

import { getOnboardingComponentFor } from '../../utils/onboardingManager';
import DeviceLimit from './deployment-wizard/DeviceLimit';
import { RolloutPatternSelection, getPhaseStartTime, validatePhases } from './deployment-wizard/PhaseSettings';
import { ForceDeploy, Retries, RolloutOptions } from './deployment-wizard/RolloutOptions';
import { ScheduleRollout } from './deployment-wizard/ScheduleRollout';
import { Devices, ReleasesWarning, Software } from './deployment-wizard/SoftwareDevices';
import type { DeploymentFormValues } from './deployment-wizard/types';
import { deploymentFormSections, useDerivedData } from './deployment-wizard/utils';

const useStyles = makeStyles()(theme => ({
  accordion: {
    backgroundColor: lighten(theme.palette.background.paper, 0.25),
    marginTop: theme.spacing(4),
    '&:before': {
      display: 'none'
    },
    [`& .${accordionSummaryClasses.content}`]: {
      margin: theme.spacing(1, 0)
    },
    [`&.${accordionClasses.expanded}`]: {
      margin: 'unset',
      marginTop: theme.spacing(4)
    }
  },
  columns: {
    columnGap: 30,
    display: 'grid',
    gridTemplateColumns: 'max-content max-content',
    '&>p': {
      marginTop: theme.spacing(3)
    }
  }
}));

const getAnchor = (element, heightAdjustment = 3) => ({
  top: element.offsetTop + element.offsetHeight / heightAdjustment,
  left: element.offsetLeft + element.offsetWidth
});

export const defaultValues: DeploymentFormValues = {
  group: null,
  release: null,
  delta: false,
  forceDeploy: false,
  maxDevices: 0,
  retries: 1,
  phases: [],
  update_control_map: { states: {} }
};

export const CreateDeployment = ({ deploymentObject = {}, onDismiss, onScheduleSubmit, onValuesChange, open }) => {
  const { canRetry, canSchedule } = useSelector(getTenantCapabilities);
  const { isHosted } = useSelector(getFeatures);
  const { createdGroup, hasDynamicGroups } = useSelector(getGroupData);
  const { hasDelta: hasDeltaEnabled } = useSelector(state => state.deployments.config) ?? {};
  const devicesById = useSelector(getDevicesById);
  const { accepted: acceptedDeviceCount, pending: hasPending } = useSelector(getDeviceCountsByStatus);
  const hasDevices = !!acceptedDeviceCount;
  const { canManageUsers } = useSelector(getUserCapabilities);
  const idAttribute = useSelector(getIdAttribute);
  const isEnterprise = useSelector(getIsEnterprise);
  const { needsDeploymentConfirmation: needsCheck, previousPhases = [], retries: previousRetries = 0 } = useSelector(getGlobalSettings);
  const onboardingState = useSelector(getOnboardingState) || {};
  const { complete: isOnboardingComplete } = onboardingState;
  const { searchedIds: releases } = useSelector(getReleaseListState);
  const releasesById = useSelector(getReleasesById);
  const groupNames = useSelector(getGroupNames);
  const dispatch = useDispatch();
  const isCreating = useRef(false);
  const [isChecking, setIsChecking] = useState(false);
  const [isExpanded, setIsExpanded] = useState(false);
  const navigate = useNavigate();
  const releaseRef = useRef();
  const groupRef = useRef();
  const deploymentAnchor = useRef();
  const { classes } = useStyles();
  const methods = useForm<DeploymentFormValues>({ mode: 'onChange', defaultValues: defaultValues });
  const { control, reset, watch } = methods;
  const formValues = watch();
  const { deploymentDeviceCount, deploymentDeviceIds, devices, filter } = useDerivedData(watch, deploymentObject.devices);

  useEffect(() => {
    dispatch(getReleases({ page: 1, perPage: 100, searchOnly: true, searchTerm: '', selectedTags: [], type: '' }));
  }, [dispatch]);

  useEffect(() => {
    if (isHosted || isEnterprise) {
      dispatch(getDeploymentsConfig());
    }
  }, [dispatch, isEnterprise, isHosted]);

  const { group, phases, release } = formValues;
  useEffect(() => {
    if (open) {
      reset({
        group: deploymentObject.group ?? defaultValues.group,
        release: deploymentObject.release ?? defaultValues.release,
        delta: deploymentObject.delta ?? defaultValues.delta,
        forceDeploy: deploymentObject.forceDeploy ?? defaultValues.forceDeploy,
        maxDevices: deploymentObject.maxDevices ?? defaultValues.maxDevices,
        retries: (deploymentObject.retries ?? previousRetries ?? 0) + 1,
        phases: deploymentObject.phases ?? defaultValues.phases,
        update_control_map: deploymentObject.update_control_map ?? defaultValues.update_control_map
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [open, reset]);

  // Notify parent of form value changes for URL param sync
  useEffect(() => {
    onValuesChange?.({
      group: formValues.group,
      release: formValues.release,
      delta: formValues.delta,
      forceDeploy: formValues.forceDeploy,
      maxDevices: formValues.maxDevices,
      retries: formValues.retries - 1,
      phases: formValues.phases,
      update_control_map: formValues.update_control_map
    });
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [JSON.stringify(formValues), onValuesChange]);

  useEffect(() => {
    if (release) {
      dispatch(advanceOnboarding(onboardingSteps.SCHEDULING_ARTIFACT_SELECTION));
      dispatch(getRelease(release.name));
    }
    dispatch(advanceOnboarding(onboardingSteps.SCHEDULING_GROUP_SELECTION));
    if (group === ALL_DEVICES) {
      dispatch(advanceOnboarding(onboardingSteps.SCHEDULING_ALL_DEVICES_SELECTION));
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [group, release?.name, dispatch]);

  const cleanUpDeploymentsStatus = () => {
    if (!window.location.search) {
      return;
    }
    const location = window.location.pathname.slice('/ui'.length);
    navigate(location); // lgtm [js/client-side-unvalidated-url-redirection]
  };

  const closeWizard = () => {
    cleanUpDeploymentsStatus();
    onDismiss();
  };

  const onScheduleSubmitClick = () => {
    if (needsCheck && !isChecking) {
      return setIsChecking(true);
    }
    isCreating.current = true;
    const { delta, forceDeploy = false, maxDevices, phases, release, update_control_map } = formValues;
    const retries = (formValues.retries ?? 1) - 1;
    const startTime = phases?.length ? phases[0].start_ts : undefined;
    const retrySetting = canRetry && retries ? { retries } : {};
    const newDeployment = {
      artifact_name: release.name,
      autogenerate_delta: delta ? delta : undefined,
      devices: (filter || group) && !devices.length ? undefined : deploymentDeviceIds,
      filter_id: filter?.id,
      all_devices: !filter && group === ALL_DEVICES,
      group: group === ALL_DEVICES || devices.length ? undefined : group,
      max_devices: maxDevices ? maxDevices : undefined,
      name: devices[0]?.id || (group ? decodeURIComponent(group) : ALL_DEVICES),
      phases: phases.length
        ? phases.map((phase, i, origPhases) => {
            phase.start_ts = getPhaseStartTime(origPhases, i, startTime);
            return phase;
          })
        : undefined,
      ...retrySetting,
      force_installation: forceDeploy,
      update_control_map: !isEmpty(update_control_map.states) ? update_control_map : undefined
    };
    if (!isOnboardingComplete) {
      dispatch(advanceOnboarding(onboardingSteps.SCHEDULING_RELEASE_TO_DEVICES));
    }
    return dispatch(createDeployment({ newDeployment }))
      .then(() => {
        // successfully retrieved new deployment
        cleanUpDeploymentsStatus();
        onScheduleSubmit();
      })
      .finally(() => {
        isCreating.current = false;
        setIsChecking(false);
      });
  };

  const disabled = isCreating.current || !(release && (deploymentDeviceCount || !!filter || group)) || !validatePhases(phases, deploymentDeviceCount);

  const hasReleases = !!Object.keys(releasesById).length;
  return (
    <BaseDrawer open={open} onClose={closeWizard} size="md" slotProps={{ header: { title: 'Create a deployment' } }}>
      <FormProvider {...methods}>
        <FormGroup>
          {!hasReleases ? (
            <ReleasesWarning />
          ) : (
            <>
              <Devices
                devicesById={devicesById}
                groupRef={groupRef}
                groupNames={groupNames}
                hasDevices={hasDevices}
                hasDynamicGroups={hasDynamicGroups}
                hasPending={hasPending}
                idAttribute={idAttribute}
                initialDevices={deploymentObject.devices}
              />
              <Software
                commonClasses={classes}
                releaseRef={releaseRef}
                releaseSelectionLocked={deploymentObject.releaseSelectionLocked}
                releases={releases}
                releasesById={releasesById}
              />
            </>
          )}
          <ScheduleRollout canSchedule={canSchedule} commonClasses={classes} />
          <Accordion className={classes.accordion} square expanded={isExpanded} onChange={() => setIsExpanded(toggle)}>
            <AccordionSummary expandIcon={<ExpandMore />}>
              <Typography variant="subtitle2">{isExpanded ? 'Hide' : 'Show'} advanced options</Typography>
            </AccordionSummary>
            <AccordionDetails>
              <Retries canManageUsers={canManageUsers} canRetry={canRetry} commonClasses={classes} defaultRetries={previousRetries} />
              <DeviceLimit />
              <RolloutPatternSelection isEnterprise={isEnterprise} previousPhases={previousPhases} />
              <RolloutOptions isEnterprise={isEnterprise} />
              <ForceDeploy />
              {hasDeltaEnabled && (
                <FormCheckbox id={deploymentFormSections.delta} control={control} label="Generate and deploy Delta Artifacts where available" />
              )}
            </AccordionDetails>
          </Accordion>
        </FormGroup>
        <div className="margin-top relative">
          {isChecking && (
            <Confirm
              classes="confirmation-overlay"
              cancel={() => setIsChecking(false)}
              action={onScheduleSubmitClick}
              message={`This will deploy ${release?.name} to ${deploymentDeviceCount} ${pluralize('device', deploymentDeviceCount)}. Are you sure?`}
              style={{ paddingLeft: 12, justifyContent: 'flex-start', maxHeight: 44 }}
            />
          )}
          <Button onClick={closeWizard} style={{ marginRight: 10 }}>
            Cancel
          </Button>
          <Button variant="contained" color="primary" ref={deploymentAnchor} disabled={disabled} onClick={onScheduleSubmitClick}>
            Create deployment
          </Button>
        </div>
        <OnboardingComponent
          releaseRef={releaseRef}
          groupRef={groupRef}
          deploymentAnchor={deploymentAnchor}
          deploymentDeviceCount={deploymentDeviceCount}
          devices={devices}
          group={group}
          release={release}
          onboardingState={onboardingState}
          createdGroup={createdGroup}
          releasesById={releasesById}
          releases={releases}
          hasDevices={hasDevices}
        />
      </FormProvider>
    </BaseDrawer>
  );
};

export default CreateDeployment;

const OnboardingComponent = ({
  releaseRef,
  groupRef,
  deploymentAnchor,
  deploymentDeviceCount,
  devices,
  group,
  release: deploymentRelease = null,
  onboardingState,
  createdGroup,
  releasesById,
  releases,
  hasDevices
}) => {
  let onboardingComponent = null;
  if (releaseRef.current && groupRef.current && deploymentAnchor.current) {
    const anchor = getAnchor(releaseRef.current);
    const groupAnchor = getAnchor(groupRef.current);
    onboardingComponent = getOnboardingComponentFor(onboardingSteps.SCHEDULING_ALL_DEVICES_SELECTION, onboardingState, { anchor: groupAnchor, place: 'right' });
    if (createdGroup) {
      onboardingComponent = getOnboardingComponentFor(
        onboardingSteps.SCHEDULING_GROUP_SELECTION,
        { ...onboardingState, createdGroup },
        { anchor: groupAnchor, place: 'right' },
        onboardingComponent
      );
    }
    if (deploymentDeviceCount && !deploymentRelease) {
      onboardingComponent = getOnboardingComponentFor(
        onboardingSteps.SCHEDULING_ARTIFACT_SELECTION,
        { ...onboardingState, selectedRelease: releasesById[releases[0]] || {} },
        { anchor, place: 'right' },
        onboardingComponent
      );
    }
    if (hasDevices && (deploymentDeviceCount || devices?.length) && deploymentRelease) {
      const buttonAnchor = {
        top: deploymentAnchor.current.parentElement.offsetTop + deploymentAnchor.current.offsetHeight / 2,
        left: deploymentAnchor.current.parentElement.offsetLeft + deploymentAnchor.current.offsetLeft + deploymentAnchor.current.offsetWidth
      };
      onboardingComponent = getOnboardingComponentFor(
        onboardingSteps.SCHEDULING_RELEASE_TO_DEVICES,
        { ...onboardingState, selectedDevice: devices.length ? devices[0] : undefined, selectedGroup: group, selectedRelease: deploymentRelease },
        { anchor: buttonAnchor, place: 'right' },
        onboardingComponent
      );
    }
  }
  return onboardingComponent;
};
