import React, { Fragment } from 'react';
import paramCase from 'param-case';
import { Margin } from 'styled-components-spacing';
import { set } from 'react-redux-values';
import { compose, graphql } from 'react-apollo';
import { connect } from 'react-redux';
import { SubmissionError, reset, startSubmit, stopSubmit } from 'redux-form';
import ReduxForm from 'declarative-redux-form';
import find from 'lodash.find';
import get from 'lodash.get';
import intercept from 'apr-intercept';
import remcalc from 'remcalc';
import Fuse from 'fuse.js';

import {
  ViewContainer,
  StatusLoader,
  Message,
  MessageDescription,
  MessageTitle,
  Divider,
  H3
} from 'joyent-ui-toolkit';

import GetMetadata from '@graphql/list-metadata.gql';
import UpdateMetadata from '@graphql/update-metadata.gql';
import DeleteMetadata from '@graphql/delete-metadata.gql';
import parseError from '@state/parse-error';
import ToolbarForm from '@components/instances/toolbar';
import Confirm from '@state/confirm';
import { addMetadata as validateMetadata } from '@state/validators';

import {
  AddForm as MetadataAddForm,
  EditForm as MetadataEditForm
} from '@components/instances/metadata';

const MENU_FORM_NAME = 'instance-metadata-list-menu';
const ADD_FORM_NAME = 'instance-metadata-add-new';
const METADATA_FORM_KEY = field => `instance-metadata-${paramCase(field)}`;

export const Metadata = ({
  metadata = [],
  addOpen,
  loading,
  error,
  shouldAsyncValidate,
  handleAsyncValidate,
  handleToggleAddOpen,
  handleUpdateExpanded,
  handleCancel,
  handleCreate,
  handleUpdate,
  handleRemove
}) => {
  const _loading = loading && !metadata.length ? <StatusLoader /> : null;

  const _add = addOpen ? (
    <Margin bottom="3">
      <ReduxForm
        form={ADD_FORM_NAME}
        onSubmit={handleCreate}
        shouldAsyncValidate={shouldAsyncValidate}
        asyncValidate={handleAsyncValidate}
      >
        {props => (
          <MetadataAddForm
            {...props}
            onCancel={() => handleToggleAddOpen(false)}
          />
        )}
      </ReduxForm>
    </Margin>
  ) : null;

  const _line = _loading ? null : (
    <Fragment>
      <Divider key="line" height={remcalc(1)} />
      <Divider key="after-line-space" height={remcalc(24)} transparent />
    </Fragment>
  );

  const _count = _loading ? null : (
    <Margin bottom="3" top="5">
      <H3>
        {metadata.length} key-value pair{metadata.length === 1 ? '' : 's'}
      </H3>
    </Margin>
  );

  const _metadata =
    !_loading &&
    metadata.map(({ form, initialValues, expanded, removing }) => (
      <Margin bottom="2">
        <ReduxForm
          form={form}
          key={form}
          initialValues={initialValues}
          destroyOnUnmount={false}
          shouldAsyncValidate={shouldAsyncValidate}
          asyncValidate={handleAsyncValidate}
          onSubmit={handleUpdate}
        >
          {props => (
            <MetadataEditForm
              {...props}
              /* yeah, we need this here too */
              initialValues={initialValues}
              onToggleExpanded={() => handleUpdateExpanded(form, !expanded)}
              onCancel={() => handleCancel(form)}
              onRemove={() => handleRemove(form)}
              expanded={expanded}
              removing={removing}
            />
          )}
        </ReduxForm>
      </Margin>
    ));

  const _error =
    error && !_metadata.length && !_loading ? (
      <Margin bottom="5">
        <Message error>
          <MessageTitle>Ooops!</MessageTitle>
          <MessageDescription>
            An error occurred while loading your metadata
          </MessageDescription>
        </Message>
      </Margin>
    ) : null;

  return (
    <ViewContainer main>
      <Margin bottom="5">
        <ReduxForm form={MENU_FORM_NAME}>
          {props => (
            <ToolbarForm
              {...props}
              searchable={!_loading}
              searchLabel="Filter metadata"
              searchPlaceholder="Search by name or value"
              actionLabel="Add Metadata"
              actionable={!_loading}
              onActionClick={() => handleToggleAddOpen(!addOpen)}
            />
          )}
        </ReduxForm>
      </Margin>
      <Divider height={remcalc(11)} transparent />
      {_line}
      {_error}
      {_loading}
      {_count}
      {_add}
      {_metadata}
    </ViewContainer>
  );
};

export default compose(
  graphql(UpdateMetadata, { name: 'updateMetadata' }),
  graphql(DeleteMetadata, { name: 'deleteMetadata' }),
  graphql(GetMetadata, {
    options: ({ match }) => ({
      ssr: false,
      pollInterval: 1000,
      variables: {
        id: get(match, 'params.instance')
      }
    }),
    props: ({ data: { loading, error, machine, refetch, ...rest } }) => {
      const values = get(machine, 'metadata', []).filter(
        ({ name = '' }) => name !== 'user-script'
      );

      const metadata = values.map(({ id, name, value }) => ({
        id,
        form: METADATA_FORM_KEY(name),
        initialValues: {
          name,
          value
        }
      }));

      const index = new Fuse(values, {
        keys: ['name', 'value']
      });

      return {
        index,
        metadata,
        instance: machine,
        loading,
        error,
        refetch
      };
    }
  }),
  connect(
    ({ values, form }, { metadata, index, ownProps }) => {
      // get search value
      const filter = get(form, `${MENU_FORM_NAME}.values.filter`, false);
      // if user is searching something, get items that match that query
      const filtered = filter
        ? index.search(filter).map(({ id }) => find(metadata, ['id', id]))
        : metadata;

      return {
        ...ownProps,
        addOpen: get(values, 'add-metadata-open', false),
        metadata: filtered.map(({ form, ...metadata }) => ({
          ...metadata,
          form,
          expanded: get(values, `${form}-expanded`, false),
          removing: get(values, `${form}-removing`, false)
        }))
      };
    },
    (dispatch, ownProps) => {
      const {
        instance,
        metadata,
        updateMetadata,
        deleteMetadata,
        refetch
      } = ownProps;

      return {
        handleCancel: form => {
          return dispatch([
            set({ name: `${form}-expanded`, value: false }),
            dispatch(reset(form))
          ]);
        },
        handleToggleAddOpen: value => {
          return dispatch(set({ name: `add-metadata-open`, value }));
        },
        handleUpdateExpanded: (form, expanded) => {
          return dispatch(set({ name: `${form}-expanded`, value: expanded }));
        },
        shouldAsyncValidate: ({ trigger }) => {
          return trigger === 'submit';
        },
        handleAsyncValidate: validateMetadata,
        handleCreate: async ({ name, value }) => {
          // call mutation
          const [err] = await intercept(
            updateMetadata({
              variables: {
                id: instance.id,
                metadata: [{ name, value }]
              }
            })
          );

          if (err) {
            // show mutation error
            throw new SubmissionError({
              _error: parseError(err)
            });
          }

          dispatch([
            // reset create new metadata form
            reset(ADD_FORM_NAME),
            stopSubmit(ADD_FORM_NAME),
            // close add form
            set({ name: `add-metadata-open`, value: false })
          ]);

          // fetch metadata again (even though we are polling)
          return refetch();
        },
        handleUpdate: async ({ name, value }, _, { form }) => {
          // call mutations
          const [err] = await intercept(
            Promise.all([
              deleteMetadata({
                variables: {
                  id: instance.id,
                  name: get(
                    find(metadata, ['form', form]),
                    'initialValues.name'
                  )
                }
              }),
              updateMetadata({
                variables: {
                  id: instance.id,
                  metadata: [{ name, value }]
                }
              })
            ])
          );

          if (err) {
            // show mutation error
            throw new SubmissionError({
              _error: parseError(err)
            });
          }

          dispatch([
            // reset form
            stopSubmit(form),
            // close card
            set({ name: `${form}-expanded`, value: false })
          ]);

          // fetch metadata again (even though we are polling)
          return refetch();
        },
        handleRemove: async form => {
          const name = get(
            find(metadata, ['form', form]),
            'initialValues.name'
          );

          if (!await Confirm(`Do you want to remove "${name}"?`)) {
            return;
          }

          dispatch([
            set({ name: `${form}-removing`, value: true }),
            startSubmit(form)
          ]);

          // call mutation
          const [err] = await intercept(
            deleteMetadata({
              variables: {
                id: instance.id,
                name
              }
            })
          );

          const flipSubmitFalse = stopSubmit(form, {
            _error: err && parseError(err)
          });

          dispatch([
            flipSubmitFalse,
            set({ name: `${form}-removing`, value: false })
          ]);

          // fetch metadata again (even though we are polling)
          return refetch();
        }
      };
    }
  )
)(Metadata);
