import { Router } from 'aurelia-router';
import { CommunicationHubClient } from './../../services/signalr/communication-hub-client';
import { CustomLogger } from './../../modules/custom-logger';
import { autoinject, bindable, bindingMode, customElement, Disposable, TaskQueue } from "aurelia-framework";
import { HttpClient } from 'aurelia-fetch-client';
import Tribute from "tributejs/src/Tribute.js";
import { ServiceBase } from '../../data/service-base';
import { ChatMessage, ChatMessageDTO, User } from "../../model/common-model";
import { AuthService } from "../../security/auth-service";
import { EntityTypeNames, KeyboardShortcut } from './../../constants/constants';
import { Config } from '../../config';
import moment from "moment";

@customElement('ui-chat')
@autoinject
export class UIChat {
  @bindable
  public entityName: string;

  @bindable
  public entityId: number;

  @bindable({ defaultBindingMode: bindingMode.fromView })
  public messagesCounter: number;

  public isLoading: boolean;
  public messageToSend: string = "";
  public messageToEdit: string = "";

  private userService: ServiceBase<User>;
  private authService: AuthService;
  private disposables: Disposable[] = [];

  public firstMessageId: number | null;
  public messageList: Array<ChatMessage>;
  public messageToDisplay: Array<Array<ChatMessage>>;
  private inputElement: HTMLElement;

  private usersList: Array<User>;

  private tribute: Tribute;
  private signalRMethodName = () => `new-message-${this.entityName}-${this.entityId}`;
  public container: HTMLElement;
  keydownHandler = (ev) => this.enter(ev);
  constructor(private router: Router, private config: Config, private httpClient: HttpClient, authService: AuthService, private taskQueue: TaskQueue, private logger: CustomLogger, private commHubclient: CommunicationHubClient) {
    this.userService = new ServiceBase<User>(EntityTypeNames.User);
    this.isLoading = false;
    this.messageList = [];
    this.messageToDisplay = [];
    this.authService = authService;
  }

  async bind() {
    await this.getFirstMessageId();
    await this.refreshChat(false);
    this.inputElement.addEventListener("keydown", this.keydownHandler);
    await this.buildTributeUsers(this.inputElement);
    this.commHubclient.subscribeToTopic(this.signalRMethodName());
    this.disposables.push(this.commHubclient.onNewMessage((topic: string, data: ChatMessage) => {
      this.messageList.push(data);
      this.messagesCounter = this.messageList.length;

      if (this.messageToDisplay.length > 0) {
        const lastGroup = this.messageToDisplay[this.messageToDisplay.length - 1];
        lastGroup.push(data);
        this.queueScrollToBottom();
      }
      this.refreshChat(false);
    }));

    this.queueScrollToBottom();
  }

  detached() {
    this.inputElement.removeEventListener('keydown', this.keydownHandler);
  }

  unbind() {
    this.commHubclient.unsubscribeToTopic(this.signalRMethodName());
    this.disposables.forEach(x => x.dispose());
  }

  async attached() {
    this.inputElement.onfocus = () => {
      window.setTimeout(() => {
        var sel, range;
        if (window.getSelection && document.createRange) {
          range = document.createRange();
          range.selectNodeContents(this.inputElement);
          sel = window.getSelection();
          sel.removeAllRanges();
          range.collapse(false);
          sel.addRange(range);
        }
      }, 1);
    };
    this.inputElement.focus();

    var ce = document.querySelector('[contenteditable]')
    ce.addEventListener('paste', function (e: any) {
      e.preventDefault()
      var paste = e.clipboardData.getData('text/plain')
      const selection = window.getSelection();
      if (!selection.rangeCount) return;
      selection.deleteFromDocument();
      selection.getRangeAt(0).insertNode(document.createTextNode(paste));
      selection.collapseToEnd();
    })
  }

  protected async refreshChat(showLoading: boolean, scrollPosition?: number) {
    this.messageList.splice(0);
    this.messageToDisplay.splice(0);
    if (this.entityId != null && this.entityName != null) {
      await this.getMessages(showLoading);
      this.messagesCounter = this.messageList.length;
    }
    if (scrollPosition != null) {
      this.taskQueue.queueTask(() => {
        this.container.scrollTop = scrollPosition;
      });
    }
    else {
      this.taskQueue.queueTask(() => {
        this.container.scrollTop = this.container.scrollHeight;
      });
    }
  }

  enter(event: KeyboardEvent) {
    if (event.key === KeyboardShortcut.Enter && !this.tribute?.isActive && event.shiftKey === false) {
      event.preventDefault();
      document.getElementById("sendMessageButton").click();
    }
  }

  queueScrollToBottom() {
    this.taskQueue.queueTask(() => {
      this.container.scrollTop = this.container.scrollHeight - this.container.offsetHeight;
    });
  }

  getData() {
    if (this.messageList.length > 0) {
      let lastMessageId: number = this.getLastMessageId();
      this.getMessages(true, lastMessageId).then((result: boolean) => {
        if (result) {
          this.taskQueue.queueTask(() => {
            if (lastMessageId != null) {
              let lastMessage = document.getElementById(lastMessageId.toString());
              if (lastMessage) {
                this.container.scrollTop = lastMessage.offsetTop;
              }
            }
          });
        }
      });
    }
  }

  private async getMessages(showLoading: boolean, lastMessageId: number = null): Promise<boolean> {
    if (lastMessageId == null) {
      lastMessageId = this.getLastMessageId();
    }

    if (this.firstMessageId != null && this.firstMessageId != lastMessageId) {
      if (showLoading) this.isLoading = true;
      let response = await this.httpClient.fetch(this.config.globalConfig.remoteServiceName + "/api/communication/messages" + "?entityId=" + this.entityId + "&entityName=" + this.entityName + "&lastMessageId=" + lastMessageId?.toString());
      if (response.ok) {
        let messages: ChatMessage[] = await response.json();
        this.messageList.unshift(...messages);

        this.orderMessageList();
      }
      else {
        await this.handleBadRequest(response);
      }
      if (showLoading) this.isLoading = false;
      return response.ok;
    }
    return false;
  }

  private getLastMessageId() {
    if (this.messageList?.length > 0) {
      return Math.min(...this.messageList.map(x => x.id));
    }
    return null;
  }

  private async getFirstMessageId() {
    let results = await this.httpClient.fetch(this.config.globalConfig.remoteServiceName + "/api/communication/firstMessageId" + "?entityId=" + this.entityId + "&entityName=" + this.entityName);
    if (results.ok) {
      this.firstMessageId = await results.json() as number;
    }
    else {
      await this.handleBadRequest(results);
    }
  }

  private async handleBadRequest(response: Response) {
    let errorReponse = await response.json();
    this.logger.LogError(errorReponse["Message"] ?? response.statusText, null, null, true);
  }

  private async sendMessage() {
    if (this.messageToSend.length > 0) {
      let messageDTO: ChatMessageDTO = {
        message: this.messageToSend,
        messageId: null,
        creatorId: this.authService.currentUser.id,
        entityId: this.entityId,
        entityName: this.entityName,
        route: this.router.currentInstruction.fragment
      }
      let result = await this.httpClient.post(this.config.globalConfig.remoteServiceName + "/api/communication/messages", JSON.stringify(messageDTO));
      if (result.ok) {
        this.messageToSend = "";
        this.inputElement.focus();
      }
      else {
        let errorReponse = await result.text();
        this.logger.LogError(errorReponse, null, null, true);
      }
    }
  }

  private orderMessageList() {
    let lastCreatorId = 0;
    let lastTime: Date;
    let messageGroup: Array<ChatMessage> = [];
    this.messageToDisplay.splice(0);
    this.messageList.forEach(message => {
      if (lastCreatorId != message.createdById || lastTime == undefined || moment(message.createdTime).diff(lastTime, "h") > 1) {
        if (moment(message.createdTime).dayOfYear() != moment(lastTime).dayOfYear() || lastCreatorId == 0) {
          message.firstMessageOfTheDay = true;
        }
        else {
          message.firstMessageOfTheDay = false;
        }
        if (messageGroup.length > 0) {
          this.messageToDisplay.push(messageGroup);
        }
        messageGroup = [];
        lastCreatorId = message.createdById;
        lastTime = message.createdTime;
      }
      message.isMyMessage = this.authService.currentUser.id == message.createdById;
      messageGroup.push(message);
    });
    this.messageToDisplay.push(messageGroup);
  }

  private onMouseOnMessage(message: ChatMessage) {
    if (message.createdById == this.authService.currentUser.id) {
      message.showButton = true;
    }
  }

  private onMouseOutMessage(message: ChatMessage) {
    message.showButton = false;
  }

  private async deleteMessage(message: ChatMessage) {
    await this.httpClient.delete(this.config.globalConfig.remoteServiceName + "/api/communication/messages/" + message.id.toString());
    this.refreshChat(false, this.container.scrollTop);
  }

  private editMessage(message: ChatMessage) {
    message.showEdit = true;
    this.messageToEdit = message.messageML;
  }

  private async sendEditMessage(message: ChatMessage) {
    await this.httpClient.put(this.config.globalConfig.remoteServiceName + "/api/communication/messages/" + message.id.toString(), JSON.stringify(this.messageToEdit));
    message.showEdit = false;
    this.messageToEdit = "";
    this.refreshChat(false, this.container.scrollTop);
  }

  private cancelEditMessage(message: ChatMessage) {
    message.showEdit = false;
    this.messageToEdit = "";
  }

  public getLocalTime(dateString: string, format: string) {
    return moment.utc(dateString).local().format(format).toString();
  }

  public async buildTributeUsers(element: Element) {
    this.usersList = await this.userService.getAllEntities();

    let tributeValues: Array<{ id: number, name: string }> = [];
    this.usersList.forEach(user => {
      tributeValues.push({ id: user.id, name: "@" + user.fullName });
    });

    this.tribute = new Tribute({
      values: tributeValues,

      // symbol or string that starts the lookup
      trigger: '@',

      // class added in the flyout menu for active item
      selectClass: "highlight",

      // function called on select that returns the content to insert
      selectTemplate: function (item) {
        return `<span data-userid=${item.original.id} contenteditable=\"false\">${item.original.name}</span>`;
      },

      // template displayed in list 
      menuItemTemplate: function (item) {
        return (item.original.name.length > 70 ? item.original.name.substring(0, 70) + "..." : item.original.name);
      },

      // column to search against in the object (accepts function or string)
      lookup: 'name',

      // specify whether a space is allowed in the middle of mentions
      allowSpaces: true,

      // template for when no match is found (optional),
      // If no template is provided, menu is hidden.
      noMatchTemplate: function () {
        return "";
      },
    });

    this.tribute.attach(element);
  }
}