import { useState, useEffect, useCallback, useMemo } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Link, useHistory, useLocation } from 'react-router-dom';
import { getBaseUrl, getParentUrl } from '@plone/volto/helpers/Url/Url';
import Helmet from '@plone/volto/helpers/Helmet/Helmet';
import {
  removeAliases,
  addAliases,
  getAliases,
  uploadAliases,
} from '@plone/volto/actions/aliases/aliases';
import { createPortal } from 'react-dom';
import {
  Button,
  Checkbox,
  Container,
  Form,
  Header,
  Input,
  Loader,
  Menu,
  Pagination,
  Radio,
  Segment,
  Table,
} from 'semantic-ui-react';
import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
import DatetimeWidget from '@plone/volto/components/manage/Widgets/DatetimeWidget';
import FormFieldWrapper from '@plone/volto/components/manage/Widgets/FormFieldWrapper';
import { ModalForm } from '@plone/volto/components/manage/Form';
import Icon from '@plone/volto/components/theme/Icon/Icon';
import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
import Toast from '@plone/volto/components/manage/Toast/Toast';
import FormattedDate from '@plone/volto/components/theme/FormattedDate/FormattedDate';
import { useClient } from '@plone/volto/hooks/client/useClient';

import backSVG from '@plone/volto/icons/back.svg';
import editingSVG from '@plone/volto/icons/editing.svg';
import map from 'lodash/map';
import { toast } from 'react-toastify';

const messages = defineMessages({
  back: {
    id: 'Back',
    defaultMessage: 'Back',
  },
  aliases: {
    id: 'URL Management',
    defaultMessage: 'URL Management',
  },
  AddUrl: {
    id: 'Add Alternative URL',
    defaultMessage: 'Add Alternative URL',
  },
  EditUrl: {
    id: 'Edit Alternative URL',
    defaultMessage: 'Edit Alternative URL',
  },
  success: {
    id: 'Success',
    defaultMessage: 'Success',
  },
  successAdd: {
    id: 'Alias has been added',
    defaultMessage: 'Alias has been added',
  },
  successUpload: {
    id: 'Aliases have been uploaded.',
    defaultMessage: 'Aliases have been uploaded.',
  },
  successRemove: {
    id: 'Aliases have been removed.',
    defaultMessage: 'Aliases have been removed.',
  },
  filterByPrefix: {
    id: 'Filter by prefix',
    defaultMessage: 'Filter by path',
  },
  manualOrAuto: {
    id: 'Manually or automatically added?',
    defaultMessage: 'Manually or automatically added?',
  },
  createdAfter: {
    id: 'Created after',
    defaultMessage: 'Created after',
  },
  createdBefore: {
    id: 'Created before',
    defaultMessage: 'Created before',
  },
  altUrlPathTitle: {
    id: 'Alternative url path (Required)',
    defaultMessage: 'Alternative URL path (Required)',
  },
  altUrlError: {
    id: 'Alternative url path must start with a slash.',
    defaultMessage: 'Alternative URL path must start with a slash.',
  },
  targetUrlPathTitle: {
    id: 'Target Path (Required)',
    defaultMessage: 'Target Path (Required)',
  },
  BulkUploadAltUrls: {
    id: 'BulkUploadAltUrls',
    defaultMessage: 'Bulk upload CSV',
  },
  CSVFile: {
    id: 'CSVFile',
    defaultMessage: 'CSV file',
  },
  Both: {
    id: 'Both',
    defaultMessage: 'Both',
  },
  Automatically: {
    id: 'Automatically',
    defaultMessage: 'Automatically',
  },
  Manually: {
    id: 'Manually',
    defaultMessage: 'Manually',
  },
  examplePath: {
    id: 'examplePath',
    defaultMessage: '/example',
  },
});

const filterChoices = [
  { label: 'Both', value: '' },
  { label: 'Automatically', value: 'no' },
  { label: 'Manually', value: 'yes' },
];

const itemsPerPageChoices = [10, 25, 50, 'All'];

const Aliases = (props) => {
  const title = props;
  const intl = useIntl();
  const dispatch = useDispatch();
  const { pathname } = useLocation();
  const history = useHistory();

  const hasAdvancedFiltering = useSelector(
    (state) => state.site.data?.features?.filter_aliases_by_date,
  );
  const hasBulkUpload = hasAdvancedFiltering !== undefined;
  const aliases = useSelector((state) => state.aliases);
  const [filterType, setFilterType] = useState(filterChoices[0]);
  const [createdBefore, setCreatedBefore] = useState(null);
  const [createdAfter, setCreatedAfter] = useState(null);
  const [aliasesToRemove, setAliasesToRemove] = useState([]);
  const [filterQuery, setFilterQuery] = useState('');
  const [activePage, setActivePage] = useState(1);
  const [itemsPerPage, setItemsPerPage] = useState(10);
  const [addModalOpen, setAddModalOpen] = useState(false);
  const [addError, setAddError] = useState(null);
  const [editingData, setEditingData] = useState(null);
  const [uploadModalOpen, setUploadModalOpen] = useState(false);
  const [uploadError, setUploadError] = useState(null);
  const [csvErrors, setCSVErrors] = useState([]);
  const isClient = useClient();

  const updateResults = useCallback(() => {
    const options = {
      query: filterQuery,
      manual: filterType.value,
      batchStart: (activePage - 1) * itemsPerPage,
      batchSize: itemsPerPage === 'All' ? 999999999999 : itemsPerPage,
    };
    if (hasAdvancedFiltering) {
      options.start = createdAfter || '';
      options.end = createdBefore || '';
    } else {
      options.datetime = createdBefore || '';
    }
    dispatch(getAliases(getBaseUrl(pathname), options));
  }, [
    activePage,
    createdAfter,
    createdBefore,
    dispatch,
    filterQuery,
    filterType.value,
    hasAdvancedFiltering,
    itemsPerPage,
    pathname,
  ]);

  // Update results after changing the page.
  // (We intentionally leave updateResults out of the deps.)
  // eslint-disable-next-line react-hooks/exhaustive-deps
  useEffect(() => updateResults(), [activePage, itemsPerPage]);

  // Calculate page count from results
  const pages = useMemo(() => {
    let pages = Math.ceil(aliases.items_total / itemsPerPage);
    if (pages === 0 || isNaN(pages)) {
      pages = '';
    }
    return pages;
  }, [aliases.items_total, itemsPerPage]);

  // Add new alias
  const handleAdd = (formData) => {
    const { altUrlPath, targetUrlPath } = formData;
    // Validate altUrlPath starts with a slash
    if (!altUrlPath || altUrlPath.charAt(0) !== '/') {
      setAddError(intl.formatMessage(messages.altUrlError));
      return;
    }
    // Remove existing alias first if we're editing it.
    const precondition = editingData
      ? dispatch(
          removeAliases('', { items: [{ path: editingData.altUrlPath }] }),
        )
      : Promise.resolve();
    precondition.then(() => {
      dispatch(
        addAliases('', {
          items: [{ path: altUrlPath, 'redirect-to': targetUrlPath }],
        }),
      )
        .then(() => {
          updateResults();
          setAddModalOpen(false);
          setEditingData(null);
          toast.success(
            <Toast
              success
              title={intl.formatMessage(messages.success)}
              content={intl.formatMessage(messages.successAdd)}
            />,
          );
        })
        .catch((error) => {
          setAddError(error.response?.body?.message);
        });
    });
  };

  // Check/uncheck an alias
  const handleCheckAlias = (alias) => {
    if (aliasesToRemove.includes(alias)) {
      setAliasesToRemove(aliasesToRemove.filter((x) => x !== alias));
    } else {
      setAliasesToRemove([...aliasesToRemove, alias]);
    }
  };

  // Remove selected aliases
  const handleRemoveAliases = () => {
    dispatch(
      removeAliases('', {
        items: aliasesToRemove.map((a) => ({ path: a })),
      }),
    ).then(() => {
      updateResults();
      toast.success(
        <Toast
          success
          title={intl.formatMessage(messages.success)}
          content={intl.formatMessage(messages.successRemove)}
        />,
      );
    });
    setAliasesToRemove([]);
  };

  // Upload CSV
  const handleBulkUpload = (formData) => {
    fetch(`data:${formData.file['content-type']};base64,${formData.file.data}`)
      .then((res) => res.blob())
      .then((blob) => {
        dispatch(uploadAliases(blob))
          .then(() => {
            updateResults();
            setUploadError(null);
            setCSVErrors([]);
            setUploadModalOpen(false);
            toast.success(
              <Toast
                success
                title={intl.formatMessage(messages.success)}
                content={intl.formatMessage(messages.successUpload)}
              />,
            );
          })
          .catch((error) => {
            setUploadError(error.response?.body?.message);
            setCSVErrors(error.response?.body?.csv_errors ?? []);
          });
      });
  };

  return (
    <div id="page-aliases">
      <Helmet title={intl.formatMessage(messages.aliases)} />
      <Container>
        <article id="content">
          <Segment.Group raised>
            <Segment className="primary">
              <FormattedMessage
                id="URL Management"
                defaultMessage="URL Management"
                values={{ title: <q>{title}</q> }}
              />
            </Segment>
            <Segment>
              <Button
                primary
                id="add-alt-url"
                onClick={() => setAddModalOpen(true)}
              >
                {intl.formatMessage(messages.AddUrl)}&hellip;
              </Button>
              {addModalOpen && (
                <ModalForm
                  open={true}
                  onSubmit={handleAdd}
                  onCancel={() => setAddModalOpen(false)}
                  title={
                    editingData
                      ? intl.formatMessage(messages.EditUrl)
                      : intl.formatMessage(messages.AddUrl)
                  }
                  submitError={addError}
                  schema={{
                    fieldsets: [
                      {
                        id: 'default',
                        fields: ['altUrlPath', 'targetUrlPath'],
                      },
                    ],
                    properties: {
                      altUrlPath: {
                        title: intl.formatMessage(messages.altUrlPathTitle),
                        description: (
                          <FormattedMessage
                            id="Enter the absolute path where the alternative url should exist. The path must start with '/'. Only URLs that result in a 404 not found page will result in a redirect occurring."
                            defaultMessage="Enter the absolute path where the alternative URL should exist. The path must start with '/'. Only URLs that result in a 404 not found page will result in a redirect occurring."
                          />
                        ),
                        placeholder: intl.formatMessage(messages.examplePath),
                      },
                      targetUrlPath: {
                        title: intl.formatMessage(messages.targetUrlPathTitle),
                        description: (
                          <FormattedMessage
                            id="Enter the absolute path of the target. Target must exist or be an existing alternative url path to the target."
                            defaultMessage="Enter the absolute path of the target. Target must exist or be an existing alternative URL path to the target."
                          />
                        ),
                        placeholder: intl.formatMessage(messages.examplePath),
                      },
                    },
                    required: ['altUrlPath', 'targetUrlPath'],
                  }}
                  formData={editingData || {}}
                />
              )}
              {hasBulkUpload && (
                <>
                  <Button onClick={() => setUploadModalOpen(true)}>
                    {intl.formatMessage(messages.BulkUploadAltUrls)}&hellip;
                  </Button>
                  {uploadModalOpen && (
                    <ModalForm
                      open={true}
                      onSubmit={handleBulkUpload}
                      onCancel={() => setUploadModalOpen(false)}
                      title={intl.formatMessage(messages.BulkUploadAltUrls)}
                      submitError={uploadError}
                      description={
                        <>
                          <p>
                            <FormattedMessage
                              id="bulkUploadUrlsHelp"
                              defaultMessage="Add many alternative URLs at once by uploading a CSV file. The first column should be the path to redirect from; the second, the path to redirect to. Both paths must be Plone-site-relative, starting with a slash (/). An optional third column can contain a date and time. An optional fourth column can contain a boolean to mark as a manual redirect (default true)."
                            />
                          </p>
                          <p>
                            <FormattedMessage
                              id="Example"
                              defaultMessage="Example"
                            />
                            :
                            <br />
                            <code>
                              /old-home-page.asp,/front-page,2019/01/27 10:42:59
                              GMT+1,true
                              <br />
                              /people/JoeT,/Users/joe-thurston,2018-12-31,false
                            </code>
                          </p>
                          {csvErrors.length ? (
                            <div
                              className="ui error message"
                              style={{ 'overflow-x': 'auto' }}
                            >
                              <pre>
                                Errors:{'\n'}
                                {csvErrors.map(
                                  (err) =>
                                    `${err.line_number}: ${err.line} - ${err.message}\n`,
                                )}
                              </pre>
                            </div>
                          ) : null}
                        </>
                      }
                      schema={{
                        fieldsets: [
                          {
                            id: 'default',
                            fields: ['file'],
                          },
                        ],
                        properties: {
                          file: {
                            title: intl.formatMessage(messages.CSVFile),
                            type: 'object',
                            factory: 'File Upload',
                          },
                        },
                        required: ['file'],
                      }}
                    />
                  )}
                </>
              )}
            </Segment>
            <Segment>
              <Form>
                <Header size="medium">
                  <FormattedMessage
                    id="All existing alternative urls for this site"
                    defaultMessage="Existing alternative URLs for this site"
                  />
                </Header>
                <Segment>
                  <Form.Field>
                    <FormFieldWrapper
                      id="filterQuery"
                      title={intl.formatMessage(messages.filterByPrefix)}
                    >
                      <Input
                        name="filter"
                        placeholder={intl.formatMessage(messages.examplePath)}
                        value={filterQuery}
                        onChange={(e) => setFilterQuery(e.target.value)}
                      />
                    </FormFieldWrapper>
                  </Form.Field>
                  <Form.Field>
                    <FormFieldWrapper
                      id="filterType"
                      title={intl.formatMessage(messages.manualOrAuto)}
                    >
                      <Form.Group inline>
                        {filterChoices.map((o, i) => (
                          <Form.Field key={i}>
                            <Radio
                              label={intl.formatMessage({ id: o.label })}
                              name="radioGroup"
                              value={o.value}
                              checked={filterType === o}
                              onChange={() => setFilterType(o)}
                            />
                          </Form.Field>
                        ))}
                      </Form.Group>
                    </FormFieldWrapper>
                  </Form.Field>
                  <Form.Field>
                    <DatetimeWidget
                      id="created-before-date"
                      title={intl.formatMessage(messages.createdBefore)}
                      dateOnly={true}
                      value={createdBefore}
                      onChange={(id, value) => {
                        setCreatedBefore(value);
                      }}
                    />
                  </Form.Field>
                  {hasAdvancedFiltering && (
                    <Form.Field>
                      <DatetimeWidget
                        id="created-after-date"
                        title={intl.formatMessage(messages.createdAfter)}
                        dateOnly={true}
                        value={createdAfter}
                        onChange={(id, value) => {
                          setCreatedAfter(value);
                        }}
                      />
                    </Form.Field>
                  )}
                  <Button onClick={() => updateResults()} primary>
                    <FormattedMessage id="Filter" defaultMessage="Filter" />
                  </Button>
                </Segment>
              </Form>
            </Segment>
            <Segment>
              <Header size="small">
                <FormattedMessage
                  id="Alternative url path → target url path (date and time of creation, manually created yes/no)"
                  defaultMessage="Alternative URL path → target URL path (date and time of creation, manually created yes/no)"
                />
              </Header>

              <Table celled compact>
                <Table.Header>
                  <Table.Row>
                    <Table.HeaderCell width="1">
                      <FormattedMessage id="Select" defaultMessage="Select" />
                    </Table.HeaderCell>
                    <Table.HeaderCell width="10">
                      <FormattedMessage id="Alias" defaultMessage="Alias" />
                    </Table.HeaderCell>
                    <Table.HeaderCell width="1">
                      <FormattedMessage id="Date" defaultMessage="Date" />
                    </Table.HeaderCell>
                    <Table.HeaderCell width="1">
                      <FormattedMessage id="Manual" defaultMessage="Manual" />
                    </Table.HeaderCell>
                  </Table.Row>
                </Table.Header>
                <Table.Body>
                  {aliases.get.loading && (
                    <Table.Row>
                      <Table.Cell colSpan="4">
                        <Loader active inline="centered" />
                      </Table.Cell>
                    </Table.Row>
                  )}
                  {aliases.items.length > 0 &&
                    aliases.items.map((alias, i) => (
                      <Table.Row key={i} verticalAlign="top">
                        <Table.Cell>
                          <Checkbox
                            onChange={(e, { value }) => handleCheckAlias(value)}
                            checked={aliasesToRemove.includes(alias.path)}
                            value={alias.path}
                          />
                        </Table.Cell>
                        <Table.Cell>
                          {alias.path}
                          <br />
                          &nbsp;&nbsp;&rarr; {alias['redirect-to']}{' '}
                          <Button
                            basic
                            style={{ verticalAlign: 'middle' }}
                            aria-label={intl.formatMessage(messages.EditUrl)}
                            onClick={() => {
                              setEditingData({
                                altUrlPath: alias.path,
                                targetUrlPath: alias['redirect-to'],
                              });
                              setAddModalOpen(true);
                            }}
                          >
                            <Icon name={editingSVG} size="18px" />
                          </Button>
                        </Table.Cell>
                        <Table.Cell>
                          <FormattedDate date={alias.datetime} />
                        </Table.Cell>
                        <Table.Cell>{`${alias.manual}`}</Table.Cell>
                      </Table.Row>
                    ))}
                </Table.Body>
              </Table>
              <div
                style={{
                  display: 'flex',
                  flexWrap: 'wrap',
                  alignItems: 'center',
                  marginBottom: '20px',
                }}
              >
                {pages && (
                  <Pagination
                    boundaryRange={0}
                    activePage={activePage}
                    ellipsisItem={null}
                    firstItem={null}
                    lastItem={null}
                    siblingRange={1}
                    totalPages={pages}
                    onPageChange={(e, { activePage }) =>
                      setActivePage(activePage)
                    }
                  />
                )}
                <Menu.Menu
                  position="right"
                  style={{ display: 'flex', marginLeft: 'auto' }}
                >
                  <Menu.Item style={{ color: 'grey' }}>
                    <FormattedMessage id="Show" defaultMessage="Show" />:
                  </Menu.Item>
                  {map(itemsPerPageChoices, (size) => (
                    <Menu.Item
                      style={{
                        padding: '0 0.4em',
                        margin: '0em 0.357em',
                        cursor: 'pointer',
                      }}
                      key={size}
                      value={size}
                      active={size === itemsPerPage}
                      onClick={(e, { value }) => {
                        setItemsPerPage(value);
                        setActivePage(1);
                      }}
                    >
                      {size}
                    </Menu.Item>
                  ))}
                </Menu.Menu>
              </div>
              <Button
                id="remove-alt-urls"
                disabled={aliasesToRemove.length === 0}
                onClick={handleRemoveAliases}
                primary
              >
                <FormattedMessage
                  id="Remove selected"
                  defaultMessage="Remove selected"
                />
              </Button>
            </Segment>
          </Segment.Group>
        </article>
      </Container>
      {isClient &&
        createPortal(
          <Toolbar
            pathname={pathname}
            hideDefaultViewButtons
            inner={
              <Link
                className="item"
                to="#"
                onClick={() => history.push(getParentUrl(pathname))}
              >
                <Icon
                  name={backSVG}
                  className="contents circled"
                  size="30px"
                  title={intl.formatMessage(messages.back)}
                />
              </Link>
            }
          />,
          document.getElementById('toolbar'),
        )}
    </div>
  );
};

export default Aliases;
