import { Utility } from '../../core/utility';
import { StorageManager, LOCATION } from '../../lib/storage-manager/storage-manager';
import { HttpClient } from '../../services/http-client/http-client';

import './exam-correct.sass';

import Cookies from 'js-cookie';
import moment from 'moment';


const EXAM_CORRECT_URL_POST = 'correct';
const EXAM_CORRECT_HEADERS = {
  'X-XSRF-TOKEN': Cookies.get('XSRF-TOKEN'),
  'Content-Type': HttpClient.ACCEPT.JSON,
  'Accept':       HttpClient.ACCEPT.JSON,
};


const EXAM_CORRECT_IDENT = 'uw-exam-correct';
const EXAM_CORRECT_PART_INPUT_ATTR = 'uw-exam-correct--part-input';
const EXAM_CORRECT_SEND_BTN_ID = 'exam-correct__send-btn';
const EXAM_CORRECT_USER_INPUT_ID = 'exam-correct__user';
const EXAM_CORRECT_USER_INPUT_STATUS_ID = 'exam-correct__user-status';
const EXAM_CORRECT_USER_INPUT_CANDIDATES_ID = 'exam-correct__user-candidates';
const EXAM_CORRECT_INPUT_BODY_ID = 'exam-correct__new';
const EXAM_CORRECT_USER_ATTR = 'exam-correct--user-id';
const EXAM_CORRECT_USER_DNAME_ATTR = 'exam-correct--user-dname';

const INPUT_EMPTY_CLASS = 'input--invalid';

// TODO get from settings
const MOMENT_FORMAT = 'DD.MM.YY HH:mm:ss';

// TODO 1 for debugging only, 3 would be a better choice
const USER_VALIDATION_MIN_LENGTH = 1;

const STATUS = {
  NONE: 0,
  SUCCESS: 1,
  AMBIGUOUS: 2,
  ERROR: 3,
  LOADING: 4,
};


@Utility({
  selector: `table[${EXAM_CORRECT_IDENT}]`,
})
export class ExamCorrect {

  _storageManager = new StorageManager('EXAM_CORRECT', '0.0.0', { location: LOCATION.SESSION, encrypted: true });

  _element;

  _sendBtn;
  _userInput;
  _userInputStatus;
  _userInputCandidates;
  _partInputs;

  constructor(element, app) {
    if (!element) {
      throw new Error('Exam Correct utility cannot be setup without an element!');
    }

    if (!app) {
      throw new Error('Exam Correct utility cannot be setup without an app!');
    }

    this._element = element;
    this._app = app;
    this._sendBtn = document.getElementById(EXAM_CORRECT_SEND_BTN_ID);
    this._userInput = document.getElementById(EXAM_CORRECT_USER_INPUT_ID);
    this._userInputStatus = document.getElementById(EXAM_CORRECT_USER_INPUT_STATUS_ID);
    this._userInputCandidates = document.getElementById(EXAM_CORRECT_USER_INPUT_CANDIDATES_ID);
    this._partInputs = [...this._element.querySelectorAll(`input[${EXAM_CORRECT_PART_INPUT_ATTR}]`)];

    if (this._sendBtn)
      this._sendBtn.addEventListener('click', this._sendCorrectionHandler.bind(this));
    else console.error('ExamCorrect utility could not detect send button!');

    if (this._userInput)
      this._userInput.addEventListener('focusout', this._validateUserInput.bind(this));
    else throw new Error('ExamCorrect utility could not detect user input!');

    if (!this._userInputStatus) {
      throw new Error('ExamCorrect utility could not detect user input status element!');
    }

    if (!this._userInputCandidates) {
      throw new Error('ExamCorrect utility could not detect user input candidate list!');
    }
  }

  destroy() {
    this._sendBtn.removeEventListener('click', this._sendCorrectionHandler);
    this._userInput.removeEventListener('change', this._validateUserInput);
    this._userInput.removeEventListener('input', this._removeInputEmptyClassHandler);
    // TODO destroy handlers on user input candidate elements
  }

  _validateUserInput() {
    const user = this._userInput.value;

    // do nothing in case of empty or too short input
    if (!user || user.length < USER_VALIDATION_MIN_LENGTH) {
      removeAllChildren(this._userInputCandidates);
      setStatus(this._userInputStatus, STATUS.NONE);
      return;
    }

    if (this._userInput.getAttribute(EXAM_CORRECT_USER_ATTR) !== null) {
      if (this._userInput.value === this._userInput.getAttribute(EXAM_CORRECT_USER_DNAME_ATTR)) {
        setStatus(this._userInputStatus, STATUS.NONE);
        return;
      } else {
        this._userInput.removeAttribute(EXAM_CORRECT_USER_ATTR);
      }
    }

    setStatus(this._userInputStatus, STATUS.LOADING);

    const body = {
      name: user,
      results: {},
      op: false,
    };

    this._app.httpClient.post({
      url: EXAM_CORRECT_URL_POST,
      headers: EXAM_CORRECT_HEADERS,
      body: JSON.stringify(body),
    }).then(
      (response) => response.json()
    ).then(
      (response) => this._processResponse(response, user)
    ).catch((error) => {
      console.error('Error while validating user input', error);
    });
  }

  _sendCorrectionHandler() {
    // refocus user input element for convenience
    this._userInput.focus();

    const user = this._userInput.value;

    // abort send if the user input is empty
    if (!user) {
      this._userInput.classList.add(INPUT_EMPTY_CLASS);
      this._userInput.addEventListener('input', this._removeInputEmptyClassHandler.bind(this), { once: true });
      return;
    }

    const results = {};
    for (const input of this._partInputs) {
      if (input.value) {
        if (isNaN(input.value)) {
          input.classList.add('input--invalid');
          input.focus();
          return;
        }

        const partKey = input.getAttribute(EXAM_CORRECT_PART_INPUT_ATTR);
        if (!partKey) throw new Error('Exam part input without part attribute!');
        results[partKey] = parseFloat(input.value);
      }
    }

    // abort send if there are no results (after validation)
    if (Object.keys(results).length <= 0) return;

    // TODO create and use template for this
    const correctionRow = document.createElement('TR');
    correctionRow.classList.add('table__row');
    const dateTD = document.createElement('TD');
    const now = moment();
    dateTD.appendChild(document.createTextNode(now.format(MOMENT_FORMAT)));
    dateTD.setAttribute('date', moment());
    const userTD = document.createElement('TD');
    userTD.appendChild(document.createTextNode(user));
    userTD.setAttribute(EXAM_CORRECT_USER_ATTR, user);
    const partTDs = this._partInputs.map((input) => {
      const partTD = document.createElement('TD');
      const partKey = input.getAttribute(EXAM_CORRECT_PART_INPUT_ATTR);
      if (results[partKey])
        partTD.appendChild(document.createTextNode(results[partKey]));
      return partTD;
    });
    const statusTD = document.createElement('TD');
    const statusDiv = document.createElement('DIV');
    statusDiv.classList.add('exam-correct--loading');
    statusTD.appendChild(statusDiv);
    [dateTD,userTD,...partTDs, statusTD].forEach((td) => {
      td.classList.add('table__td');
      correctionRow.appendChild(td);
    });
    const tableBody = this._element.querySelector(`tbody:not(#${EXAM_CORRECT_INPUT_BODY_ID})`);
    tableBody.insertBefore(correctionRow, tableBody.firstChild);

    // clear input values on validation success
    // TODO only clear input on post success
    [this._userInput, ...this._partInputs].forEach(clearInput);
    setStatus(this._userInputStatus, STATUS.NONE);

    const body = {
      name: user,
      results: results,
      op: true,
    };

    this._app.httpClient.post({
      url: EXAM_CORRECT_URL_POST,
      headers: EXAM_CORRECT_HEADERS,
      body: JSON.stringify(body),
    }).then(
      (response) => response.json()
    ).then(
      (response) => this._processResponse(response, user)
    ).catch((error) => {
      console.error('Error while processing response', error);
    });
  }

  _processResponse(response, user) {
    console.log('WIP _processResponse', response, user);

    if (response) {
      if (response.status === 'no-op') {
        if (response.users) {
          // delete candidate list entries from previous requests
          removeAllChildren(this._userInputCandidates);

          // show error if there are no matches for this input
          if (response.users.length === 0) {
            setStatus(this._userInputStatus, STATUS.FAILURE);
            return;
          }

          // directly accept response if there is exactly one candidate (input not ambiguous)
          if (response.users.length === 1) {
            const candidate = response.users[0];
            this._userInput.value = candidate['display-name'];
            this._userInput.setAttribute(EXAM_CORRECT_USER_ATTR, candidate.id);
            this._userInput.setAttribute(EXAM_CORRECT_USER_DNAME_ATTR, candidate['display-name']);
            setStatus(this._userInputStatus, STATUS.SUCCESS);
            return;
          }

          setStatus(this._userInputStatus, STATUS.AMBIGUOUS);
          // TODO how to destroy candidate handlers?
          response.users.forEach((userCandidate) => {
            const candidateItem = document.createElement('li');
            candidateItem.innerHTML = userToHTML(userCandidate);
            candidateItem.setAttribute(EXAM_CORRECT_USER_ATTR, userCandidate.id);
            
            const acceptCandidateHandler = () => {
              this._userInput.value = userCandidate['display-name'] || userCandidate.surname || userCandidate['mat-nr'];
              this._userInput.setAttribute(EXAM_CORRECT_USER_ATTR, userCandidate.id);
              
              // remove all candidates on accept
              removeAllChildren(this._userInputCandidates);

              setStatus(this._userInputStatus, STATUS.SUCCESS);

              // TODO focus first part result input element for convenience
            };
            candidateItem.addEventListener('click', acceptCandidateHandler);

            this._userInputCandidates.appendChild(candidateItem);
          });
        } else {
          setStatus(this._userInputStatus, STATUS.FAILURE);
        }

        return;
      }
    
      for (let row of [...this._element.rows]) {
        const userElem = row.cells.item(1);
        const userIdent = userElem && userElem.getAttribute(EXAM_CORRECT_USER_ATTR);
        if (userIdent === user) {
          let status = STATUS.ERROR;
          switch (response.status) {
            // TODO fetch update time from response and replace
            case 'success':
              status = STATUS.SUCCESS;
              if (response.user) {
                userElem.setAttribute(EXAM_CORRECT_USER_ATTR, response.user.id);
                userElem.innerHTML = userToHTML(response.user);
              }
              // TODO replace results with results from response
              // TODO set edit button visibility
              break;
            case 'ambiguous':
              // TODO show tooltip with error message
              // TODO set edit button visibility
              status = STATUS.AMBIGUOUS;
              if (response.users) {
                this._showUserList(userElem, response.users);
              }
              break;
            case 'failure':
              status = STATUS.FAILURE;
              break;
            default:
              // TODO show tooltip with 'invalid response'
              // TODO set edit button visibility
              console.error('Invalid response');
          }
          row.querySelectorAll('.exam-correct--loading').forEach((elem) => {
            setStatus(elem, status);
          });
          break;
        }
      }
    }
  }

  _removeInputEmptyClassHandler() {
    console.log('removeclass');
    if (this._userInput.value) {
      this._userInput.classList.remove(INPUT_EMPTY_CLASS);
    } else {
      this._userInput.addEventListener('input', this._removeInputEmptyClassHandler, { once: true });
    }
  }

  // TODO better name
  _showUserList(elem, users) {
    if (users) {
      removeAllChildren(elem);
      const list = document.createElement('UL');
      for (const user of users) {
        const listItem = document.createElement('LI');
        listItem.innerHTML = userToHTML(user);
        listItem.setAttribute(EXAM_CORRECT_USER_ATTR, user.id);
        listItem.setAttribute(EXAM_CORRECT_USER_DNAME_ATTR, user['display-name']);
        // TODO destroy event handler
        //const acceptCandidateHandler = () => {
        //  elem.innerHTML = userToHTML(user);
        //};
        //listItem.addEventListener('click', acceptCandidateHandler);
        list.appendChild(listItem);
      }
      elem.appendChild(list);
    } else {
      console.error('Unable to show users from invalid response');
    }
  }

}

// TODO move to general util section?
function clearInput(inputElement) {
  inputElement.value = null;
}
function removeAllChildren(element) {
  while (element.firstChild) {
    element.removeChild(element.firstChild);
  }
}


function userToHTML(user) {
  if (user && user['display-name'] && user['surname']) {
    return user['display-name'].replace(new RegExp(user['surname']), `<strong>${user['surname']}</strong>`) + (user['mat-nr'] ? ` (${user['mat-nr']})` : '');
  } else {
    console.error('Unable to format invalid user response');
    return '';
  }
}

function setStatus(elem, status) {
  const successClasses = ['fas', 'fa-fw', 'fa-check', 'exam-correct--success'];
  const ambiguousClasses = ['fas', 'fa-fw', 'fa-question', 'exam-correct--ambiguous'];
  const errorClasses = ['fas', 'fa-fw', 'fa-times', 'exam-correct--error'];
  const loadingClasses = ['fas', 'fa-fw', 'fa-spinner-third', 'exam-correct--loading'];

  elem.classList.remove(...successClasses, ...ambiguousClasses, ...errorClasses, ...loadingClasses);

  switch (status) {
    case STATUS.NONE:
      break;
    case STATUS.SUCCESS:
      elem.classList.add(...successClasses);
      break;
    case STATUS.AMBIGUOUS:
      elem.classList.add(...ambiguousClasses);
      break;
    case STATUS.FAILURE:
      elem.classList.add(...errorClasses);
      break;
    case STATUS.LOADING:
      elem.classList.add(...loadingClasses);
      break;
    default:
      console.error('Invalid user input status!');
  }
}

