import axios from 'axios';
import { cloneDeep, isEqual } from 'lodash-es';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { Col, Grid, Row } from 'react-bootstrap';
import { connect } from 'react-redux';
import {
  createTagsByInvestigationId,
  getTagsByInvestigationId,
  updateTagsByInvestigationId,
} from '../../../api/icat/icatPlus';
import Loader from '../../../components/Loader';
import NewOrEditTagPanel from '../../../components/Logbook/Tag/NewOrEditTagPanel';
import TagListMenu from '../../../components/Logbook/Tag/TagListMenu';
import TagListPanel from '../../../components/Logbook/Tag/TagListPanel';
import UserMessage from '../../../components/UserMessage';
import {
  ERROR_MESSAGE_TYPE,
  INFO_MESSAGE_TYPE,
} from '../../../constants/eventTypes';

/**
 * Container which holds the logic to list, create and edit tag.
 */
class TagsManagerContainer extends Component {
  constructor(props) {
    super(props);
    this.state = {
      availableTags: [],
      errorMessge: null,
      isEditingOrCreatingTag: false, // false when listing tags. True when editing or creacting tag
      isFetching: true,
      isFetched: false,
      tag: null, // tag being edited, null otherwise
      userMessage: null, // an object which structure is { type: XXX, text:ZZZ }
    };

    this.createTag = this.createTag.bind(this);
    this.getTags = this.getTags.bind(this);
    this.onEditTagButtonClicked = this.onEditTagButtonClicked.bind(this);
    this.onNewTagButtonClicked = this.onNewTagButtonClicked.bind(this);
    this.onSaveButtonClicked = this.onSaveButtonClicked.bind(this);
    this.requestTags = this.requestTags.bind(this);
    this.setUserMessage = this.setUserMessage.bind(this);
    this.updateTag = this.updateTag.bind(this);
  }

  render() {
    if (this.state.isFetching) {
      return <Loader message="Loading tags..." spacedOut />;
    }

    if (this.state.isEditingOrCreatingTag) {
      return (
        <WrapperWithUserMessage userMessage={this.state.userMessage}>
          <NewOrEditTagPanel
            onSaveButtonClicked={this.onSaveButtonClicked}
            investigationId={this.props.investigationId}
            panelHeaderText={this.state.tag ? 'Edit tag' : 'New tag'}
            onCancelButtonClicked={() =>
              this.setState({ isEditingOrCreatingTag: false })
            }
            tag={this.state.tag}
          />
        </WrapperWithUserMessage>
      );
    }

    if (this.state.availableTags.length === 0) {
      return (
        <WrapperWithUserMessage userMessage={this.state.userMessage}>
          <NoTagGrid />
          <TagListMenu
            onNewTagButtonClicked={this.onNewTagButtonClicked}
            logbookContext={this.props.logbookContext}
          />
        </WrapperWithUserMessage>
      );
    }

    return (
      <WrapperWithUserMessage userMessage={this.state.userMessage}>
        <TagListMenu
          onNewTagButtonClicked={this.onNewTagButtonClicked}
          logbookContext={this.props.logbookContext}
        />
        <TagListPanel
          availableTags={this.state.availableTags}
          isTagEditionEnabled
          onEditButtonClicked={this.onEditTagButtonClicked}
        />
      </WrapperWithUserMessage>
    );
  }

  componentDidMount() {
    this.requestTags().then((tags) => this.setState({ availableTags: tags }));
  }

  /**
   * Function Triggered when the user clicks the New button in the menu
   */
  onNewTagButtonClicked() {
    this.setState({ isEditingOrCreatingTag: true, userMessage: null });
  }

  /**
   * Function triggered when the use clicks on the EDit button for a given tag
   * @param {*} tag tag to be edited
   */
  onEditTagButtonClicked(tag) {
    if (tag) {
      this.setState({ isEditingOrCreatingTag: true, tag, userMessage: null });
    }
  }

  /**
   * Function triggered when the user clicks on the save button during tag creation or edition
   * @param {object} tag tag to be created or edited
   */
  onSaveButtonClicked(tag) {
    if (this.state.tag) {
      // the tag is to be edited
      this.updateTag(tag)
        .then((tag) => {
          const availableTags = cloneDeep(this.state.availableTags);
          const tagToBeUpdatedIndex = availableTags.findIndex((availableTag) =>
            isEqual(availableTag._id, tag._id)
          );
          availableTags.splice(tagToBeUpdatedIndex, 1, tag);
          this.setState({
            availableTags,
            isEditingOrCreatingTag: false,
            tag: null,
          });
          this.setUserMessage(
            INFO_MESSAGE_TYPE,
            'Tag was successfully updated'
          );
        })
        .catch(() => {
          this.setUserMessage(
            ERROR_MESSAGE_TYPE,
            'Error. The tag was not edited.'
          );
        });
    } else {
      // the tag is to be created
      this.createTag(tag)
        .then(() => this.requestTags())
        .then((tags) => {
          this.setState({ availableTags: tags, isEditingOrCreatingTag: false });
          this.setUserMessage(
            INFO_MESSAGE_TYPE,
            'Tag was successfully created'
          );
        })
        .catch(() => {
          this.setUserMessage(
            ERROR_MESSAGE_TYPE,
            'Error. The tag was not created.'
          );
        });
    }
  }

  /**
   * Request available tags. This function does not perform the axios call itself. The latter is isolated in the function getTags()
   */
  requestTags() {
    return new Promise((resolve, reject) => {
      if (this.props.investigationId && this.props.user.sessionId) {
        this.setState({ isFetching: true, errorMessage: null });
        this.getTags()
          .then((response) => {
            this.setState({ isFetching: false, isFetched: true });
            resolve(response.data.tags);
          })
          .catch((error) => {
            this.setState({ isFetching: false, isFetched: false });
            this.setUserMessage(
              ERROR_MESSAGE_TYPE,
              'An error occured while retrieving the tag list.'
            );
            reject(error);
          });
      } else {
        console.log('requestTags() Missing props');
        this.setState({
          availableTags: [],
          isFetching: false,
          isFetched: false,
        });
        reject();
      }
    });
  }

  /**
   * Get available tags
   * @returns {Promise}
   */
  getTags() {
    return axios({
      method: 'get',
      url: getTagsByInvestigationId(
        this.props.user.sessionId,
        this.props.investigationId
      ),
    });
  }

  /**
   * Set a message which will be displayed to the user
   * @param {string} type message type
   * @param {string} text text of the message
   */
  setUserMessage(type, message) {
    this.setState({ userMessage: { type, text: message } });
  }

  /**
   * Create a tag
   * @param {object} tag tag to be created
   * @returns {Promise}
   */
  createTag(tag) {
    if (tag) {
      return new Promise((resolve, reject) => {
        axios({
          method: 'post',
          url: createTagsByInvestigationId(
            this.props.user.sessionId,
            this.props.investigationId
          ),
          data: tag,
        })
          .then((data) => resolve(data.data))
          .catch(() => {
            reject();
          });
      });
    }
  }

  /**
   * Update a tag
   * @param {object} tag tag to be updated
   * @returns {Promise}
   */
  updateTag(tag) {
    if (tag) {
      return new Promise((resolve, reject) => {
        axios({
          method: 'put',
          url: updateTagsByInvestigationId(
            this.props.user.sessionId,
            this.props.investigationId,
            tag._id
          ),
          data: tag,
        })
          .then((data) => {
            resolve(data.data);
          })
          .catch(() => {
            reject();
          });
      });
    }
  }
}

/**
 * Redux related function which is used to map local props with redux state
 * @param {*} state redux state
 */
function mapStateToProps(state) {
  return {
    logbookContext: state.logbook.context,
    user: state.user,
  };
}

export default connect(mapStateToProps)(TagsManagerContainer);

TagsManagerContainer.propTypes = {
  /** Investigation identifier */
  investigationId: PropTypes.string.isRequired,
  /**  */
  logbookContext: PropTypes.object,
};

function NoTagGrid() {
  return (
    <Grid fluid>
      <h3> Tags currently available in this proposal</h3>

      <Row style={{ marginLeft: 30, marginRight: 30, marginTop: 20 }}>
        <UserMessage
          message="There is no tag in this proposal yet."
          isTextCentered
          type={INFO_MESSAGE_TYPE}
        />
      </Row>
    </Grid>
  );
}

const TagUserMessage = (props) => {
  if (props.userMessage) {
    return (
      <Grid fluid>
        <Row>
          <Col xs={12}>
            <UserMessage
              type={props.userMessage.type}
              message={props.userMessage.text}
            >
              {' '}
            </UserMessage>
          </Col>
        </Row>
      </Grid>
    );
  }
  return null;
};

function WrapperWithUserMessage(props) {
  return (
    <>
      <TagUserMessage userMessage={props.userMessage} />
      {props.children}
    </>
  );
}
