// Copyright 2021 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, useRef, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link as RouterLink } from 'react-router';

import { InfoOutlined as InfoIcon, Launch as LaunchIcon } from '@mui/icons-material';
import { Button, Typography } from '@mui/material';
import { makeStyles } from 'tss-react/mui';

import { ContentSection } from '@northern.tech/common-ui/ContentSection';
import DocsLink from '@northern.tech/common-ui/DocsLink';
import EnterpriseNotification from '@northern.tech/common-ui/EnterpriseNotification';
import Loader from '@northern.tech/common-ui/Loader';
import Time from '@northern.tech/common-ui/Time';
import MenderTooltip from '@northern.tech/common-ui/helptips/MenderTooltip';
import storeActions from '@northern.tech/store/actions';
import {
  ALL_DEVICES,
  AUDIT_LOGS_TYPES,
  BEGINNING_OF_TIME,
  BENEFITS,
  DEVICE_CONNECT_STATES,
  TIMEOUTS,
  checkPermissionsObject,
  uiPermissionsById
} from '@northern.tech/store/constants';
import { formatAuditlogs } from '@northern.tech/store/locationutils';
import { getCurrentSession, getTenantCapabilities, getUserCapabilities } from '@northern.tech/store/selectors';
import { getDeviceFileDownloadLink } from '@northern.tech/store/thunks';
import { createDownload } from '@northern.tech/utils/helpers';

import FileTransfer from '../troubleshoot/FileTransfer';
import TroubleshootContent from '../troubleshoot/TerminalWrapper';

const { setSnackbar } = storeActions;

const useStyles = makeStyles()(theme => ({
  buttonStyle: { textTransform: 'none', textAlign: 'left' },
  connectionIcon: { marginRight: theme.spacing() },
  content: { maxWidth: 1280 },
  title: { marginRight: theme.spacing(0.5) },
  troubleshootButton: { marginRight: theme.spacing(2) },
  tierNotice: { maxWidth: 500 }
}));

export const PortForwardLink = () => (
  <MenderTooltip
    arrow
    title={
      <div style={{ whiteSpace: 'normal' }}>
        <h3>Port forwarding</h3>
        <p>Port forwarding allows you to troubleshoot or use services on or via the device, without opening any ports on the device itself.</p>
        <p>
          To enable port forwarding you will need to install and configure the necessary software on the device and your workstation. Follow the link to learn
          more.
        </p>
      </div>
    }
  >
    <DocsLink className="flexbox centered margin-left-x-small margin-right-x-small" path="add-ons/port-forward">
      <Button variant="text" endIcon={<LaunchIcon />}>
        Enable port forwarding
      </Button>
    </DocsLink>
  </MenderTooltip>
);

export const DeviceConnectionNote = ({ children }) => {
  const { classes } = useStyles();
  return (
    <div className="flexbox muted">
      <InfoIcon className={classes.connectionIcon} fontSize="small" />
      <Typography className={classes.buttonStyle} variant="body1">
        {children}
      </Typography>
    </div>
  );
};

export const DeviceConnectionMissingNote = () => (
  <DeviceConnectionNote>
    The troubleshoot add-on does not seem to be enabled on this device.
    <br />
    Please <DocsLink path="add-ons/remote-terminal" title="see the documentation" /> for a description on how it works and how to enable it.
  </DeviceConnectionNote>
);

export const DeviceDisconnectedNote = ({ lastConnectionTs }) => (
  <DeviceConnectionNote>
    The troubleshoot add-on is not currently connected on this device, it was last connected on <Time value={lastConnectionTs} />.
    <br />
    Please <DocsLink path="add-ons/remote-terminal" title="see the documentation" /> for more information.
  </DeviceConnectionNote>
);

export const TroubleshootButton = ({ disabled, item, onClick }) => {
  const { classes } = useStyles();
  return (
    <Button className={classes.troubleshootButton} onClick={() => onClick(item.key)} disabled={disabled} startIcon={item.icon}>
      <Typography className={classes.buttonStyle} variant="subtitle2">
        {item.title}
      </Typography>
    </Button>
  );
};

const deviceAuditlogType = AUDIT_LOGS_TYPES.find(type => type.value === 'device');

const tabs = {
  terminal: {
    title: 'Remote terminal',
    value: 'terminal',
    canShow: ({ canTroubleshoot, canWriteDevices, groupsPermissions }, { group }) =>
      (canTroubleshoot && canWriteDevices) || checkPermissionsObject(groupsPermissions, uiPermissionsById.connect.value, group, ALL_DEVICES),
    Component: TroubleshootContent
  },
  transfer: { title: 'File transfer', value: 'transfer', canShow: ({ canTroubleshoot }) => canTroubleshoot, Component: FileTransfer }
};

export const DeviceConnection = ({ device }) => {
  const [socketClosed, setSocketClosed] = useState();
  const [availableTabs, setAvailableTabs] = useState(Object.values(tabs));
  const [downloadPath, setDownloadPath] = useState('');
  const [file, setFile] = useState();
  const [socketInitialized, setSocketInitialized] = useState(undefined);
  const [uploadPath, setUploadPath] = useState('');
  const closeTimer = useRef();
  const initTimer = useRef();

  const userCapabilities = useSelector(getUserCapabilities);
  const { canAuditlog, canTroubleshoot } = userCapabilities;
  const { hasAuditlogs, hasDeviceConnect } = useSelector(getTenantCapabilities);
  const { token } = useSelector(getCurrentSession);
  const { classes } = useStyles();
  const { connect_status, connect_updated_ts, isOffline, tier } = device;
  const [connectionStatus, setConnectionStatus] = useState(connect_status);

  //TODO: move source of truth to store constants
  const tierEligible = tier !== 'micro';

  const dispatch = useDispatch();
  const dispatchedSetSnackbar = useCallback((...args) => dispatch(setSnackbar(...args)), [dispatch]);

  useEffect(() => {
    if (!socketClosed) {
      return;
    }
    clearTimeout(closeTimer.current);
    closeTimer.current = setTimeout(() => setSocketClosed(false), TIMEOUTS.fiveSeconds);
  }, [socketClosed]);

  useEffect(() => {
    setConnectionStatus(connect_status);
    clearTimeout(initTimer.current);
    if (connectionStatus) {
      return;
    }
    initTimer.current = setTimeout(() => {
      setConnectionStatus(!connect_status || isOffline ? DEVICE_CONNECT_STATES.unknown : connect_status);
    }, TIMEOUTS.fiveSeconds);
    return () => clearTimeout(initTimer.current);
  }, [connect_status, connectionStatus, device.id, isOffline]);

  useEffect(() => {
    const allowedTabs = Object.values(tabs).reduce((accu, tab) => {
      if (tab.canShow(userCapabilities, device) && connectionStatus === DEVICE_CONNECT_STATES.connected) {
        accu.push(tab);
      }
      return accu;
    }, []);
    setAvailableTabs(allowedTabs);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [connectionStatus, JSON.stringify(device), JSON.stringify(userCapabilities)]);

  const onDownloadClick = useCallback(
    path => {
      setDownloadPath(path);
      dispatch(setSnackbar('Downloading file'));
      dispatch(getDeviceFileDownloadLink({ deviceId: device.id, path }))
        .unwrap()
        .then(address => {
          const filename = path.substring(path.lastIndexOf('/') + 1) || 'file';
          createDownload(address, filename, token);
        });
    },
    [dispatch, device.id, token]
  );

  return (
    <ContentSection
      isAddOn
      postTitle={
        <>
          {hasDeviceConnect && connectionStatus !== DEVICE_CONNECT_STATES.unknown && canTroubleshoot && tierEligible && <PortForwardLink />}
          {hasDeviceConnect && canAuditlog && hasAuditlogs && (
            <Button
              component={RouterLink}
              variant="text"
              to={`/auditlog?${formatAuditlogs({ pageState: { type: deviceAuditlogType, detail: device.id, startDate: BEGINNING_OF_TIME } }, {})}`}
            >
              List all log entries for this device
            </Button>
          )}
          <EnterpriseNotification id={BENEFITS.deviceTroubleshoot.id} />
        </>
      }
      title="Troubleshooting"
    >
      {hasDeviceConnect &&
        (tierEligible ? (
          <div className={`flexbox column ${classes.content}`}>
            {!connectionStatus && (
              <div className="flexbox centered">
                <Loader show />
              </div>
            )}
            {connectionStatus === DEVICE_CONNECT_STATES.unknown && <DeviceConnectionMissingNote />}
            {connectionStatus === DEVICE_CONNECT_STATES.disconnected && <DeviceDisconnectedNote lastConnectionTs={connect_updated_ts} />}
            {availableTabs.map(({ Component, title, value }) => (
              <div key={value}>
                <h4 className="margin-top-large">{title}</h4>
                <Component
                  device={device}
                  downloadPath={downloadPath}
                  file={file}
                  onDownload={onDownloadClick}
                  setDownloadPath={setDownloadPath}
                  setFile={setFile}
                  setSnackbar={dispatchedSetSnackbar}
                  setSocketClosed={setSocketClosed}
                  setSocketInitialized={setSocketInitialized}
                  setUploadPath={setUploadPath}
                  socketInitialized={socketInitialized}
                  uploadPath={uploadPath}
                  userCapabilities={userCapabilities}
                />
              </div>
            ))}
          </div>
        ) : (
          <div className={`margin-top flexbox centered align-center`}>
            <Typography className={classes.tierNotice}>
              The Troubleshoot Add-on cannot be used with a Micro tier device. Please <DocsLink path="overview/device-tiers" title="see the documentation" /> to
              learn how to change a device to the Standard tier.
            </Typography>
          </div>
        ))}
    </ContentSection>
  );
};

export default DeviceConnection;
