// 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 { useCallback, useEffect, useMemo, useState } from 'react';
import { useFormContext } from 'react-hook-form';
import { useSelector } from 'react-redux';

import { ErrorOutlined as ErrorOutlineIcon } from '@mui/icons-material';
import { ExpandLess as ExpandLessIcon, ExpandMore as ExpandMoreIcon } from '@mui/icons-material';
import { Alert, Button, TextField, Tooltip, Typography } from '@mui/material';
import { makeStyles } from 'tss-react/mui';

import { getDeviceIdentityText } from '@northern.tech/common-ui/DeviceIdentity';
import InfoText from '@northern.tech/common-ui/InfoText';
import { Link } from '@northern.tech/common-ui/Link';
import { ControlledAutoComplete } from '@northern.tech/common-ui/forms/Autocomplete';
import { ALL_DEVICES, ATTRIBUTE_SCOPES, DEPLOYMENT_TYPES, DEVICE_FILTERING_OPTIONS, DEVICE_STATES } from '@northern.tech/store/constants';
import { formatDeviceSearch } from '@northern.tech/store/locationutils';
import { getDeviceLimits } from '@northern.tech/store/selectors';
import { useAppDispatch } from '@northern.tech/store/store';
import { getExistingSoftwareTags, getSystemDevices, getUpdateTypes } from '@northern.tech/store/thunks';
import { stringToBoolean } from '@northern.tech/utils/helpers';
import { useWindowSize } from '@northern.tech/utils/resizehook';
import pluralize from 'pluralize';
import validator from 'validator';

import { HELPTOOLTIPS } from '../../helptips/HelpTooltips';
import { MenderHelpTooltip } from '../../helptips/MenderTooltip';
import { SoftwareArtifactFilter } from './ReleaseArtifactFilter';
import type { DeploymentFormValues } from './types';
import { deploymentFormSections, useDerivedData } from './utils';

const { isUUID } = validator;

const useStyles = makeStyles()(theme => ({
  infoStyle: {
    minWidth: 400,
    borderBottom: 'none'
  },
  selection: { minWidth: 'min-content', maxWidth: theme.spacing(50), minHeight: 96 },
  releaseSelect: { maxWidth: '400px', minWidth: '235px' },
  releaseSelectText: { minWidth: 0, flexGrow: 1 }
}));

export const getDevicesLink = ({ devices, filters = [], group, name, groupId }) => {
  let devicesLink = '/devices';
  if (filters.length) {
    return `${devicesLink}?${formatDeviceSearch({ pageState: {}, filters, selectedGroup: group, groupId })}`;
  }
  // older deployments won't have the filter set so we have to try to guess their targets based on other information
  if (devices.length && (!name || isUUID(name))) {
    devicesLink = `${devicesLink}?${devices.map(({ id }) => `id=${id}`).join('&')}`;
    if (devices.length === 1) {
      const { systemDeviceIds = [] } = devices[0];
      devicesLink = `${devicesLink}${systemDeviceIds.map(id => `&id=${id}`).join('')}`;
    }
  } else if (group) {
    devicesLink = `${devicesLink}?${formatDeviceSearch({ pageState: {}, filters, selectedGroup: group, groupId })}`;
  }
  return devicesLink;
};

const deploymentFiltersToTargetText = ({ devicesById, filter, idAttribute }) => {
  const { name, filters = [] } = filter;
  if (name) {
    return name;
  }
  if (
    filters.some(
      ({ operator, scope, value }) => scope === ATTRIBUTE_SCOPES.identity && value === DEVICE_STATES.accepted && operator === DEVICE_FILTERING_OPTIONS.$eq.key
    )
  ) {
    return ALL_DEVICES;
  }
  const groupFilter = filters.find(
    ({ operator, scope, key }) => scope === ATTRIBUTE_SCOPES.system && operator === DEVICE_FILTERING_OPTIONS.$eq.key && key === 'group'
  );
  if (groupFilter) {
    return groupFilter.value;
  }
  return filters
    .reduce((accu, { operator, scope, key, value }) => {
      if (!(key === 'id' && scope === ATTRIBUTE_SCOPES.identity)) {
        return accu;
      }
      if (operator === DEVICE_FILTERING_OPTIONS.$in.key) {
        const values = Array.isArray(value) ? value : [value];
        const devices = values.map(deviceId => getDeviceIdentityText({ device: devicesById[deviceId], idAttribute }));
        return [...accu, ...devices];
      }
      accu.push(getDeviceIdentityText({ device: devicesById[value], idAttribute }));
      return accu;
    }, [])
    .join(', ');
};

export const getDeploymentTargetText = ({ deployment, devicesById, idAttribute }) => {
  const { devices = {}, filter = {}, group = '', name = '', type = DEPLOYMENT_TYPES.software } = deployment;
  const text = deploymentFiltersToTargetText({ devicesById, filter, idAttribute });
  if (text) {
    return text;
  }
  let deviceList = Array.isArray(devices) ? devices : Object.values(devices);
  if (isUUID(name) && devicesById[name]) {
    deviceList = [devicesById[name]];
  }
  if (type !== DEPLOYMENT_TYPES.configuration && (!deviceList.length || group || (deployment.name !== undefined && !isUUID(name)))) {
    return (group || name) ?? '';
  }
  return deviceList.map(device => getDeviceIdentityText({ device, idAttribute })).join(', ') || name;
};

export const ReleasesWarning = ({ lacksReleases }) => (
  <div className="flexbox align-items-center">
    <ErrorOutlineIcon fontSize="small" style={{ marginRight: 4, top: 4, color: 'rgb(171, 16, 0)' }} />
    <InfoText>
      There are no {lacksReleases ? 'compatible ' : ''}artifacts available.{lacksReleases ? <br /> : ' '}
      <Link to="/software">Upload one to the repository</Link> to get started.
    </InfoText>
  </div>
);

export const Devices = ({ devicesById, groupRef, groupNames, hasDevices, hasDynamicGroups, hasPending, idAttribute, initialDevices = [] }) => {
  const { classes } = useStyles();
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const size = useWindowSize();
  const dispatch = useAppDispatch();
  const { watch } = useFormContext<DeploymentFormValues>();

  const group = watch(deploymentFormSections.group);
  const { deploymentDeviceCount, devices, filter } = useDerivedData(watch, initialDevices);
  const device = useMemo(() => (devices.length === 1 ? devices[0] : {}), [devices]);

  useEffect(() => {
    const { attributes = {} } = device;
    const { mender_is_gateway } = attributes;
    if (!device.id || !stringToBoolean(mender_is_gateway)) {
      return;
    }
    dispatch(getSystemDevices({ id: device.id, perPage: 500 }));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [device.id, device.attributes?.mender_is_gateway, dispatch]);

  const { deviceText, devicesLink, targetDeviceCount, targetDevicesText } = useMemo(() => {
    const devicesLink = getDevicesLink({ devices, group, filters: filter?.filters, groupId: filter?.id });
    let deviceText = getDeploymentTargetText({ deployment: { devices, filter, group }, devicesById, idAttribute });
    let targetDeviceCount = deploymentDeviceCount;
    let targetDevicesText = `${deploymentDeviceCount} ${pluralize('devices', deploymentDeviceCount)}`;
    if (device?.id) {
      const { attributes = {}, systemDeviceIds = [] } = device;
      const { mender_is_gateway } = attributes;
      deviceText = `${getDeviceIdentityText({ device, idAttribute })}${stringToBoolean(mender_is_gateway) ? ' (System)' : ''}`;
      // here we hope the number of systemDeviceIds doesn't exceed the queried 500 and add the gateway device
      targetDeviceCount = systemDeviceIds.length + 1;
    } else if (group) {
      deviceText = '';
      targetDevicesText = 'All devices';
      targetDeviceCount = 2;
      if (group !== ALL_DEVICES) {
        targetDevicesText = `${targetDevicesText} in this group`;
        targetDeviceCount = deploymentDeviceCount;
      }
    }
    return { deviceText, devicesLink, targetDeviceCount, targetDevicesText };
  }, [devices, filter, group, devicesById, idAttribute, deploymentDeviceCount, device]);

  return (
    <>
      <Typography variant="subtitle1" className="margin-bottom-x-small">
        Select a device group to target
      </Typography>
      <div ref={groupRef} className={classes.selection}>
        {deviceText ? (
          <TextField value={deviceText} label={pluralize('device', devices.length)} disabled className={classes.infoStyle} />
        ) : (
          <div>
            <ControlledAutoComplete
              name={deploymentFormSections.group}
              id="deployment-device-group-selection"
              autoSelect
              autoHighlight
              filterSelectedOptions
              handleHomeEndKeys
              disabled={!(hasDevices || hasDynamicGroups)}
              options={groupNames}
              renderInput={params => <TextField {...params} placeholder="Select a device group" className={classes.textField} />}
            />
            {!(hasDevices || hasDynamicGroups) && (
              <InfoText style={{ marginTop: '10px' }}>
                <ErrorOutlineIcon style={{ marginRight: '4px', fontSize: '18px', top: '4px', color: 'rgb(171, 16, 0)', position: 'relative' }} />
                There are no connected devices.{' '}
                {hasPending ? (
                  <span>
                    <Link to="/devices/pending">Accept pending devices</Link> to get started.
                  </span>
                ) : (
                  <span>
                    <Link to="/help/get-started">Read the help pages</Link> for help with connecting devices.
                  </span>
                )}
              </InfoText>
            )}
          </div>
        )}
        {!!targetDeviceCount && (
          <InfoText>
            {targetDevicesText} will be targeted. <Link to={devicesLink}>View the {pluralize('devices', targetDeviceCount)}</Link>
          </InfoText>
        )}
      </div>
    </>
  );
};

const MCU_ARTIFACT_SIZE_LIMIT = 5 * 1024 ** 2;

export const Software = ({ commonClasses, releaseRef, releaseSelectionLocked, releases, releasesById }) => {
  const [releaseFilterOpened, setReleaseFilterOpened] = useState(false);
  const [showSizeWarning, setShowSizeWarning] = useState(false);
  const deviceLimits = useSelector(getDeviceLimits);
  const dispatch = useAppDispatch();
  const { classes } = useStyles();
  const { watch, setValue } = useFormContext<DeploymentFormValues>();

  const deploymentRelease = watch(deploymentFormSections.release);
  // resolve the full Release from releasesById to get artifacts/device_types_compatible
  const selectedRelease = deploymentRelease?.name ? releasesById[deploymentRelease.name] : undefined;
  const hasMicroDevicesOnly = deviceLimits.micro !== 0 && !(deviceLimits.standard && deviceLimits.system);

  useEffect(() => {
    dispatch(getExistingSoftwareTags());
    dispatch(getUpdateTypes());
  }, [dispatch]);

  useEffect(() => {
    if (hasMicroDevicesOnly) {
      setShowSizeWarning(Boolean(selectedRelease?.artifacts?.some(({ size }) => size > MCU_ARTIFACT_SIZE_LIMIT)));
    }
  }, [hasMicroDevicesOnly, selectedRelease]);

  const releaseItems = releases.map(rel => releasesById[rel]);
  const onReleaseSelectionChange = useCallback(
    software => {
      if (software !== deploymentRelease) {
        setValue(deploymentFormSections.release, software);
      }
    },
    [deploymentRelease, setValue]
  );
  const compatibleTypes = deploymentRelease?.compatible_types || [];
  const devicetypesInfo = (
    <Tooltip title={<p>{compatibleTypes.join(', ')}</p>} placement="bottom">
      <Link>
        {compatibleTypes.length} {deploymentRelease?.kind === 'manifest' ? 'system' : 'device'} {pluralize('types', compatibleTypes.length)}
      </Link>
    </Tooltip>
  );

  return (
    <>
      <div className="flexbox align-items-center margin-bottom-x-small">
        <Typography variant="subtitle1">Select software to deploy</Typography>
        <MenderHelpTooltip className="margin-left-small" small id={HELPTOOLTIPS.groupDeployment.id} />
      </div>
      <div className={commonClasses.columns}>
        <div ref={releaseRef} className={classes.selection}>
          {releaseSelectionLocked ? (
            <TextField value={deploymentRelease?.name} label="Software" disabled className={classes.infoStyle} />
          ) : (
            <>
              <SoftwareArtifactFilter
                onSelect={onReleaseSelectionChange}
                selectedSoftware={deploymentRelease?.name}
                open={releaseFilterOpened}
                onClose={() => setReleaseFilterOpened(false)}
              />
              <Button
                size="large"
                color="neutral"
                variant="outlined"
                className={classes.releaseSelect}
                endIcon={releaseFilterOpened ? <ExpandLessIcon /> : <ExpandMoreIcon />}
                onClick={() => setReleaseFilterOpened(!releaseFilterOpened)}
              >
                <span className={`${classes.releaseSelectText} text-overflow`}>{deploymentRelease?.name ?? 'Select software'}</span>
              </Button>
            </>
          )}
          {!releaseItems.length ? (
            <ReleasesWarning lacksReleases />
          ) : (
            !!compatibleTypes.length && <InfoText style={{ marginBottom: 0 }}>This software is compatible with {devicetypesInfo}.</InfoText>
          )}
        </div>
      </div>
      {showSizeWarning && (
        <div className={`margin-bottom-large ${commonClasses.columns}`}>
          <Alert severity="warning">Artifacts larger than 5MB will not be deployed to Micro tier devices.</Alert>
        </div>
      )}
    </>
  );
};
