// 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, useRef, useState } from 'react';
import Dropzone from 'react-dropzone';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate } from 'react-router-dom';

import { Typography } from '@mui/material';
import { makeStyles } from 'tss-react/mui';

import DetailsTable from '@northern.tech/common-ui/DetailsTable';
import { Link } from '@northern.tech/common-ui/Link';
import Loader from '@northern.tech/common-ui/Loader';
import Pagination from '@northern.tech/common-ui/Pagination';
import { RelativeTime } from '@northern.tech/common-ui/Time';
import storeActions from '@northern.tech/store/actions';
import { DEPLOYMENT_ROUTES, DEVICE_LIST_DEFAULTS, SORTING_OPTIONS, TIMEOUTS, canAccess as canShow } from '@northern.tech/store/constants';
import {
  getFeatures,
  getHasReleases,
  getIsUploading,
  getReleaseListState,
  getReleasesList,
  getSelectedReleases,
  getUserCapabilities
} from '@northern.tech/store/selectors';
import { getExistingReleaseTags, getReleases, getUpdateTypes, removeReleases, selectRelease, setReleasesListState } from '@northern.tech/store/thunks';

import ReleaseDetails, { DeleteReleasesConfirmationDialog, ReleaseQuickActions } from './ReleaseDetails';
import AddTagsDialog from './dialogs/AddTags';

const { setSnackbar } = storeActions;

const refreshArtifactsLength = TIMEOUTS.refreshLong;

const columns = [
  {
    key: 'name',
    title: 'Name',
    render: ({ name }) => name,
    sortable: true,
    defaultSortDirection: SORTING_OPTIONS.asc,
    canShow
  },
  {
    key: 'artifacts-count',
    title: 'Number of artifacts',
    render: ({ artifacts = [] }) => artifacts.length,
    canShow
  },
  {
    key: 'tags',
    title: 'Tags',
    render: ({ tags = [] }) => tags.join(', ') || '-',
    defaultSortDirection: SORTING_OPTIONS.asc,
    sortable: true,
    canShow
  },
  {
    key: 'modified',
    title: 'Last modified',
    render: ({ modified }) => <RelativeTime updateTime={modified} />,
    defaultSortDirection: SORTING_OPTIONS.desc,
    sortable: true,
    canShow
  }
];

const useStyles = makeStyles()(() => ({
  empty: { margin: '8vh auto' }
}));

const { page: defaultPage, perPage: defaultPerPage } = DEVICE_LIST_DEFAULTS;

const EmptyState = ({ canUpload, className = '', dropzoneRef, uploading, onDrop, onUpload }) => (
  <div className={`dashboard-placeholder fadeIn ${className}`} ref={dropzoneRef}>
    <Dropzone activeClassName="active" disabled={uploading} multiple={false} noClick={true} onDrop={onDrop} rejectClassName="active">
      {({ getRootProps, getInputProps }) => (
        <div {...getRootProps({ className: uploading ? 'dropzone disabled muted' : 'dropzone' })} onClick={() => onUpload()}>
          <input {...getInputProps()} disabled={uploading} />
          <p>
            There are no Releases yet.{' '}
            {canUpload && (
              <>
                <Link>Upload an Artifact</Link> to create a new Release
              </>
            )}
          </p>
        </div>
      )}
    </Dropzone>
  </div>
);

export const ReleasesList = ({ className = '', onFileUploadClick }) => {
  const repoRef = useRef();
  const dropzoneRef = useRef();
  const artifactTimer = useRef<ReturnType<typeof setInterval> | undefined>();
  const uploading = useSelector(getIsUploading);
  const releasesListState = useSelector(getReleaseListState);
  const {
    isLoading,
    page = defaultPage,
    perPage = defaultPerPage,
    searchTerm,
    sort = {},
    searchTotal,
    selection: selectedRows,
    selectedTags = [],
    total,
    type
  } = releasesListState;
  const hasReleases = useSelector(getHasReleases);
  const features = useSelector(getFeatures);
  const releases = useSelector(getReleasesList);
  const userCapabilities = useSelector(getUserCapabilities);
  const selectedReleases = useSelector(getSelectedReleases);
  const dispatch = useDispatch();
  const { classes } = useStyles();
  const [addTagsDialog, setAddTagsDialog] = useState(false);
  const [deleteDialogConfirmation, setDeleteDialogConfirmation] = useState(false);
  const navigate = useNavigate();

  const { canUploadReleases } = userCapabilities;
  const { key: attribute, direction } = sort;

  useEffect(() => {
    dispatch(getReleases({ searchTerm: '', searchOnly: true, page: 1, perPage: 1, selectedTags: [], type: '' }))
      .unwrap()
      .then(() => dispatch(setReleasesListState({ isLoading: false })));
    dispatch(getExistingReleaseTags());
    dispatch(getUpdateTypes());
    clearInterval(artifactTimer.current);
    artifactTimer.current = setInterval(() => dispatch(getReleases()), refreshArtifactsLength);
    return () => {
      clearInterval(artifactTimer.current);
      dispatch(setReleasesListState({ selection: [] }));
    };
  }, [dispatch]);

  const onSelect = useCallback(id => dispatch(selectRelease(id)), [dispatch]);

  const onChangeSorting = sortKey => {
    let sort = { key: sortKey, direction: direction === SORTING_OPTIONS.asc ? SORTING_OPTIONS.desc : SORTING_OPTIONS.asc };
    if (sortKey !== attribute) {
      sort = { ...sort, direction: columns.find(({ key }) => key === sortKey)?.defaultSortDirection ?? SORTING_OPTIONS.desc };
    }
    dispatch(setReleasesListState({ page: 1, sort }));
  };

  const onChangePagination = (page, currentPerPage = perPage) => dispatch(setReleasesListState({ page, perPage: currentPerPage }));

  const onDrop = (acceptedFiles, rejectedFiles) => {
    if (acceptedFiles.length) {
      onFileUploadClick(acceptedFiles[0]);
    }
    if (rejectedFiles.length) {
      dispatch(setSnackbar(`File '${rejectedFiles[0].name}' was rejected. File should be of type .mender`));
    }
  };

  const applicableColumns = useMemo(
    () =>
      columns.reduce((accu, column) => {
        if (column.canShow({ features })) {
          accu.push(column);
        }
        return accu;
      }, []),
    // eslint-disable-next-line react-hooks/exhaustive-deps
    [JSON.stringify(features)]
  );

  const onDeleteRelease = releases => {
    onSelectionChange(releases);
    setDeleteDialogConfirmation(true);
  };

  const onSelectionChange = useCallback((selection: number[] = []) => dispatch(setReleasesListState({ selection })), [dispatch]);

  const deleteReleases = useCallback(() => {
    dispatch(removeReleases(selectedReleases.map(({ name }) => name))).then(() => {
      setDeleteDialogConfirmation(false);
      onSelectionChange([]);
    });
  }, [dispatch, onSelectionChange, selectedReleases]);

  const onTagRelease = releases => {
    onSelectionChange(releases);
    setAddTagsDialog(true);
  };

  const onCreateDeployment = useCallback(
    selection => {
      if (selection.length !== 1) {
        return;
      }
      const { name: releaseName } = selectedReleases[0];
      navigate(`${DEPLOYMENT_ROUTES.active.route}?open=true&release=${encodeURIComponent(releaseName)}`, { state: { internal: true } });
    },
    [navigate, selectedReleases]
  );

  const actionCallbacks = {
    onCreateDeployment,
    onDeleteRelease,
    onTagRelease
  };

  const isFiltering = !!(selectedTags.length || type || searchTerm);
  const potentialTotal = isFiltering ? searchTotal : total;
  if (!hasReleases) {
    return (
      <EmptyState
        canUpload={canUploadReleases}
        className={classes.empty}
        dropzoneRef={dropzoneRef}
        uploading={uploading}
        onDrop={onDrop}
        onUpload={onFileUploadClick}
      />
    );
  }

  return (
    <div className={className}>
      {isLoading === undefined ? (
        <Loader show />
      ) : !potentialTotal ? (
        <Typography className="margin-top muted align-center margin-right">There are no Releases {isFiltering ? 'for the filter selection' : 'yet'}</Typography>
      ) : (
        <>
          <DetailsTable
            columns={applicableColumns}
            items={releases}
            onItemClick={onSelect}
            sort={sort}
            onChangeSorting={onChangeSorting}
            tableRef={repoRef}
            onRowSelected={onSelectionChange}
            selectedRows={selectedRows}
          />
          <div className="flexbox">
            <Pagination
              className="margin-top-none"
              count={potentialTotal}
              rowsPerPage={perPage}
              onChangePage={onChangePagination}
              onChangeRowsPerPage={newPerPage => onChangePagination(1, newPerPage)}
              page={page}
            />
            <Loader show={isLoading} small />
          </div>
          {selectedReleases?.length > 0 && <ReleaseQuickActions actionCallbacks={actionCallbacks} />}
          {addTagsDialog && <AddTagsDialog selectedReleases={selectedReleases} onClose={() => setAddTagsDialog(false)} />}
          {deleteDialogConfirmation && <DeleteReleasesConfirmationDialog onClose={() => setDeleteDialogConfirmation(false)} onSubmit={deleteReleases} />}
        </>
      )}
      <ReleaseDetails />
    </div>
  );
};

export default ReleasesList;
