import {
  AfterViewInit,
  Component,
  ElementRef,
  OnDestroy,
  OnInit, QueryList,
  ViewChild, ViewChildren
} from '@angular/core';
import { DetailsFormBase } from '@objects-view/details-form-base';
import { ITicketModel } from '@fleio-api/plugins/tickets/model/ticket.model';
import { ConfigService } from '@shared/config/config.service';
import { UntypedFormBuilder } from '@angular/forms';
import { ActivatedRoute, NavigationEnd, Router } from '@angular/router';
import { ITicketCreateOptionsModel } from '@fleio-api/plugins/tickets/model/ticket-create-options.model';
import { forkJoin, of, Subscription } from 'rxjs';
import { TicketsApiService } from '@fleio-api/plugins/tickets/tickets-api.service';
import { NotificationService } from '@shared/ui-api/notification.service';
import { UploadInterfaceComponent } from '@shared/ui/upload-interface/upload-interface.component';
import { TicketAttachmentsApiService } from '@fleio-api/plugins/tickets/ticket-attachments-api.service';
import { HttpResponse } from '@angular/common/http';
import { ITicketAttachmentModel } from '@fleio-api/plugins/tickets/model/ticket-attachment.model';
import { FleioId } from '@fleio-api/base-model/base-fleio-object.model';
import { Location } from '@angular/common';
import { IUserModel } from '@fleio-api/client-user/model/user.model';
import { TicketUpdatesApiService } from '@fleio-api/plugins/tickets/ticket-updates-api.service';
import { TicketNotesApiService } from '@fleio-api/plugins/tickets/ticket-notes-api.service';
import { AppLocalStorageService } from '@shared/ui-api/app-local-storage.service';
import { IActionResult } from '@objects-view/interfaces/actions/action-result';
import { MatDialog } from '@angular/material/dialog';
import { TicketLinkingApiService } from '@fleio-api/plugins/tickets/ticket-linking-api.service';
import { ITicketReplyModel } from '@fleio-api/plugins/tickets/model/ticket-reply.model';
import { IAction } from '@objects-view/interfaces/actions/action';
import {
  RemoveTicketLinkDialogComponent
} from '@shared/plugins/common-dialogs/tickets/remove-ticket-link-dialog/remove-ticket-link-dialog.component';
import {
  InsertPredefinedReplyDialogComponent
} from '@shared/plugins/common-dialogs/tickets/insert-predefined-reply-dialog/insert-predefined-reply-dialog.component';
import {
  TicketEditMessageDialogComponent
} from '@shared/plugins/common-dialogs/tickets/ticket-edit-message-dialog/ticket-edit-message-dialog.component';
import { EditorComponent } from '@tinymce/tinymce-angular';
import { ThemingService } from '@shared/ui/theming/theming.service';
import { TinyMCEAttachments, TinymceHelper } from '@shared/ui-api/helpers/tinymce-helper';
import { IIcon } from '@shared/ui/common/interfaces/icon';
import * as _moment from 'moment/moment';
import { RefreshService } from '@shared/ui-api/refresh.service';
import {
  TicketAddNoteDialogComponent
} from '@shared/plugins/common-dialogs/tickets/ticket-add-note-dialog/ticket-add-note-dialog.component';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
import { DateFormatterPipe } from '@shared/ui/common/pipes/date-formatter.pipe';

let TINYMCE_ATTACHMENTS: TinyMCEAttachments;

@Component({
  selector: 'app-ticket-details-tab',
  templateUrl: './ticket-details-tab.component.html',
  styleUrls: ['./ticket-details-tab.component.scss']
})
export class TicketDetailsTabComponent extends DetailsFormBase<ITicketModel>
  implements OnInit, AfterViewInit, OnDestroy {

  @ViewChild('uploadInterface') uploadInterface: UploadInterfaceComponent;
  @ViewChild('textareaWrapper') textareaWrapper: ElementRef;
  @ViewChild('tinyMceArea') tinyMceArea: EditorComponent;
  @ViewChildren('replyLinks') replyLinks: QueryList<ElementRef>;
  @ViewChildren('replyMessageContainers') replyMessageContainer: QueryList<ElementRef>;
  tinyMceHelper = new TinymceHelper();
  public readonly datePipe: DateFormatterPipe;
  tinyMCEOptions = null;
  createOptions: ITicketCreateOptionsModel;
  replyForm = this.formBuilder.group({
    replyText: ['']
  });
  loading = false;
  hoveredItems: {
    [itemId: string]: boolean;
  } = {};
  replyTooltip = $localize`Reply`;
  actionsTooltip = $localize`Actions`;
  attachmentIcon = {
    name: 'attachment',
    class: 'fl-icons',
    fontSize: '17px',
    verticalAlign: 'sub',
  } as IIcon;
  currentFragment = null;
  fragmentSubscription: Subscription;
  scrolledToTextarea = false;
  topOffsetSubscription: Subscription;
  routerSubscription: Subscription;
  topOffset: number;
  ticketDescription: SafeHtml;

  descriptionExpanded: boolean;
  expandedRepliesByIndex = {};
  allExpanded = false;

  constructor(public config: ConfigService, private formBuilder: UntypedFormBuilder, private activatedRoute: ActivatedRoute,
              private ticketsApiService: TicketsApiService, private router: Router,
              private notificationService: NotificationService,
              private ticketAttachmentsApiService: TicketAttachmentsApiService,
              private ticketUpdatesApiService: TicketUpdatesApiService,
              private ticketNotesApiService: TicketNotesApiService,
              private appLocalStorageService: AppLocalStorageService,
              private ticketLinkingApiService: TicketLinkingApiService,
              private readonly matDialog: MatDialog,
              private themingService: ThemingService,
              private location: Location,
              private refreshService: RefreshService,
              private domSanitizer: DomSanitizer,
  ) {
    super();
    this.datePipe = new DateFormatterPipe(config);
    TINYMCE_ATTACHMENTS = new TinyMCEAttachments(this.ticketAttachmentsApiService);
    this.routerSubscription = this.router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        this.setTimestamps();
      }
    });
  }

  private refreshTicket() {
    this.refreshService.refresh(() => {
      this.loading = false; // for when we refresh the form due to reply adding
    });
  }

  replyToReply(quote: string, createdBy: IUserModel) {
    const splitQuote = quote.split('\n');
    const newQuote = [];
    splitQuote.forEach((element) => {
      element = element.replace('>', '> > ');
      newQuote.push(element);
    });
    const newQuoteJoined = newQuote.join('\n');
    let replyText: string;
    if (createdBy) {
      replyText = newQuoteJoined + '<em>' + 'Quoted from ' + createdBy.full_name + '</em><p></p>';
    } else {
      replyText = newQuoteJoined;
    }
    this.replyForm.controls.replyText.setValue(replyText);
    if (this.textareaWrapper) {
      this.textareaWrapper.nativeElement.scrollIntoView();
    }
  };

  removeMessage(replyId: FleioId, messageType: string) {
    if (messageType === 'ticketupdate') {
      this.notificationService.confirmDialog(
        {
          title: $localize`Remove reply`,
          message: $localize`Do you want to remove the reply?`
        }
      ).subscribe(result => {
        if (result?.button === 'yes') {
          this.ticketUpdatesApiService.delete(replyId).subscribe(() => {
            this.notificationService.showMessage($localize`Reply removed.`);
            this.refreshTicket();
          });
        }
      });
    } else {
      this.notificationService.confirmDialog(
        {
          title: $localize`Remove note ${replyId}`,
          message: $localize`Do you want to remove the note?`
        }
      ).subscribe(result => {
        if (result?.button === 'yes') {
          this.ticketNotesApiService.delete(replyId).subscribe(() => {
            this.notificationService.showMessage($localize`Note removed.`);
            this.refreshTicket();
          });
        }
      });
    }
  }

  openAttachment(attachmentId: FleioId) {
    if (this.config && this.config.current) {
      const popup = window.open('');
      this.config.authService.generateOtpToken().subscribe(token => {
        popup.location.href = `${this.config.current.urls.backendApiUrl}plugins/tickets/ticket_attachments/${attachmentId}/load_file?fleio-token=${token}`;
      });
    } else {
      this.notificationService.showMessage($localize`Cannot get config settings.`);
    }
  }

  downloadAttachment(attachmentId: FleioId) {
    if (this.config && this.config.current) {
      this.config.authService.generateOtpToken().subscribe(token => {
        window.location.href = `${this.config.current.urls.backendApiUrl}plugins/tickets/ticket_attachments/${attachmentId}/download_file?fleio-token=${token}`;
      });
    } else {
      this.notificationService.showMessage($localize`Cannot get config settings.`);
    }
  }

  removeAttachment(attachmentId: FleioId, fileName: string) {
    this.notificationService.confirmDialog(
      {
        title: $localize`Remove attachment ${fileName || attachmentId}`,
        message: $localize`Do you want to remove the attachment?`
      }
    ).subscribe(result => {
      if (result?.button === 'yes') {
        this.refreshTicket();
        this.ticketAttachmentsApiService.delete(attachmentId).subscribe(() => {
          this.notificationService.showMessage($localize`Attachment removed.`);
        });
      }
    });
  }

  replyToTicketActions(action: IAction) {
    if (action.name === $localize`Add note ...`) {
      this.addNoteFromDialog();
      return of(null);
    }
    const value = this.replyForm.value;
    if (!value.replyText) {
      this.notificationService.showMessage($localize`Please add a message.`);
      return of(null);
    }
    const closeTicket = action?.internalName === 'reply_and_close';
    this.loading = true;
    const fileInputs = this.uploadInterface.getFileInputs();
    const allFiles = [];
    for (const fileInput of fileInputs) {
      if (fileInput && fileInput.nativeElement.files.length) {
        // on ticket reply, for one input there is at most one file
        allFiles.push(fileInput.nativeElement.files[0]);
      }
    }
    if (allFiles.length) {
      let fd: FormData;
      const queriesList = [];
      for (const file of allFiles) {
        fd = new FormData();
        fd.append('data', file);
        fd.append('file_name', file.name);
        queriesList.push(this.ticketAttachmentsApiService.createWithUpload(fd));
      }
      forkJoin(queriesList).subscribe({
        next: responses => {
          let attachmentIDs = '';
          responses.forEach((queryResponse: HttpResponse<ITicketAttachmentModel>, index) => {
            attachmentIDs += queryResponse.body.id;
            if (index < responses.length - 1) {
              attachmentIDs += ',';
            }
          });
          this.tinyMceArea.editor.uploadImages().then(() => {
            this.addReply(this.tinyMceArea.editor.getContent(), attachmentIDs, true, closeTicket);
          });
          return of(null);
        }, error: error => {
          if (error?.error?.detail) {
            this.notificationService.showMessage(error.error.detail);
          }
          this.loading = false;
        }
      });
      return of(null);
    } else {
      this.tinyMceArea.editor.uploadImages().then(() => {
        this.addReply(this.tinyMceArea.editor.getContent(), '', false, closeTicket);
      });
      return of(null);
    }
  }

  private addNoteFromDialog() {
    return this.matDialog.open(
      TicketAddNoteDialogComponent, {
        data: {
          ticketID: this.object.id,
        },
        autoFocus: false,
      }).afterClosed().subscribe(result => {
      if (!result) {
        return;
      }
      this.refreshTicket();
      return {message: result} as IActionResult;
    });
  }

  addReply(replyText: string, associatedAttachments: string, resetFileInputs = false, closeTicket = false) {
    associatedAttachments = TINYMCE_ATTACHMENTS.appendAttachments(associatedAttachments);
    this.ticketsApiService.addReply(this.object.id, replyText, associatedAttachments, closeTicket).subscribe({
      next: () => {
        if (resetFileInputs) {
          this.uploadInterface.resetFileInputs();
        }
        this.notificationService.showMessage($localize`Reply added`);
        if (this.createOptions && this.createOptions.user_signature) {
          this.replyForm.controls.replyText.setValue('<br>' + this.createOptions.user_signature);
        } else {
          this.replyForm.controls.replyText.setValue('');
        }
        this.refreshTicket();
      }, error: error => {
        this.loading = false;
        if (error && error.error && error.error.detail) {
          this.notificationService.showMessage(error.error.detail);
        }
      }
    }).add(() => {
      TINYMCE_ATTACHMENTS.resetAttachments();
    });
  }

  getLinkedTicketRedirect(ticketId: string) {
    if (this.config) {
      return this.config.getPanelUrl(`tickets/${ticketId}`);
    }
    return '#';
  }

  removeLinking(ticketId: string) {
    this.ticketLinkingApiService.list({
      ticket: this.object.id,
      linked_ticket: ticketId
    }).subscribe(response => {
      if (response && response.objects && response.objects.length) {
        const linkingId = response.objects[0].id;
        return this.matDialog.open(
          RemoveTicketLinkDialogComponent, {
            data: {
              ticket: this.object,
              linkingId,
            },
            autoFocus: false,
          }).afterClosed().subscribe(result => {
          if (!result) {
            return;
          }
          this.notificationService.showMessage($localize`Ticket linking removed successfully.`);
          this.refreshTicket();
          return {message: result} as IActionResult;
        });
      } else {
        this.notificationService.showMessage($localize`Could not retrieve ticket link relation`);
      }
    }, () => {
      this.notificationService.showMessage($localize`Could not retrieve ticket link relation`);
    });
  }

  editMessage(item: ITicketReplyModel) {
    return this.matDialog.open(
      TicketEditMessageDialogComponent, {
        data: {
          ticketReply: item,
        },
        autoFocus: false,
      }).afterClosed().subscribe(result => {
      if (!result) {
        return;
      }
      this.notificationService.showMessage($localize`Message updated successfully.`);
      this.refreshTicket();
      return {message: result} as IActionResult;
    });
  }

  tinymceUploadHandler(blobInfo, success, failure, progress) {
    const attachmentIdentifier = `${blobInfo.id()}${new Date().getTime()}`;
    const fd = new FormData();
    fd.append('data', blobInfo.blob());
    fd.append('file_name', blobInfo.filename());
    fd.append('custom_id', attachmentIdentifier);
    return TINYMCE_ATTACHMENTS.attachmentsApiService.createWithUpload(fd).toPromise().then((resp) => {
      if (resp?.body?.id) {
        TINYMCE_ATTACHMENTS.attachmentsIDs.push(resp.body.id);
        TINYMCE_ATTACHMENTS.blobsToReplace[blobInfo.blobUri()] = attachmentIdentifier;
        return `cid:${attachmentIdentifier}`;
      }
    });
  }

  ngOnInit(): void {
    super.ngOnInit();
    this.descriptionExpanded = !this.config.current.settings.collapsedTicketRepliesFeature;

    this.objectController.dataChanged$.subscribe(() => {
      if (this.object) {
        this.formatHtml();
      }
    })

    if (this.config.current) {
      this.tinyMCEOptions = this.themingService.getTinyMceOptions(this.tinymceUploadHandler);
    }
    if (this.objectController) {
      this.objectController.actionCallback = (action: IAction) => this.replyToTicketActions(action);
    }
    this.createOptions = this.activatedRoute.snapshot.data.createOptions;
    if (this.object && this.object.unread) {
      this.ticketsApiService.markAsRead(this.object.id).subscribe(() => {
      });
    }
    if (this.object) {
      this.topOffsetSubscription = this.themingService.observableAppTopOffset.subscribe(value => {
        this.topOffset = value;
      });
      this.replyForm.controls.replyText.valueChanges.subscribe(
        replyText => {
          this.appLocalStorageService.setTicketsReplyText(this.object.id, replyText);
        }
      );
      const temporarySavedReply = this.appLocalStorageService.getTicketsReplyText(this.object.id);
      if (temporarySavedReply) {
        this.replyForm.controls.replyText.setValue(temporarySavedReply);
      } else if (this.createOptions.user_signature) {
        this.replyForm.controls.replyText.setValue('<br>' + this.createOptions.user_signature);
      }
      this.setTimestamps();
    }
  }

  private setTimestamps() {
    // add timestamps
    const currentMoment = new Date();
    this.object.timestamp_display = this.getTimestampDisplay(this.object.created_at, currentMoment);
    this.object.timestamp = new Date(this.object.created_at).valueOf().toString();
    this.object.routerLink = this.config.getPanelUrl(`tickets/${this.object.id}`);
    this.object.fragment = `${this.object.timestamp}`;
    for (const item of this.object.replies_and_notes) {
      item.timestamp_display = this.getTimestampDisplay(item.created_at, currentMoment);
      item.timestamp = new Date(item.created_at).valueOf().toString();
      item.routerLink = this.config.getPanelUrl(`tickets/${this.object.id}`);
      item.fragment = `${item.timestamp}`;
    }
  }

  private scrollToTextarea() {
    // wait for images first before scrolling
    const images = document.querySelectorAll('.ticket-message img');
    if (images && images.length) {
      let loadedImages = 0;
      const listener = () => {
        loadedImages += 1;
        if (images.length === loadedImages) {
          if (this.textareaWrapper && !this.scrolledToTextarea) {
            this.scrolledToTextarea = true;
            this.textareaWrapper.nativeElement.scrollIntoView({behavior: 'smooth'});
          }
        }
      };
      images.forEach(image => {
        image.addEventListener('load', listener);
        image.addEventListener('error', listener);
      });
    } else {
      if (this.textareaWrapper && !this.scrolledToTextarea) {
        this.scrolledToTextarea = true;
        this.textareaWrapper.nativeElement.scrollIntoView({behavior: 'smooth'});
      }
    }
  }

  public goToAnchor(anchorID: string, changeLocation = false, event: PointerEvent | MouseEvent = null) {
    if (event) {
      if (event.type === 'click' && event.ctrlKey) {
        // let the browser open the new tab
        return;
      }
      event.preventDefault();
    }
    if (changeLocation) {
      this.location.go(this.config.getPanelUrl(`tickets/${this.object.id}#${anchorID}`));
    }

    setTimeout(() => {
      const replyLink = this.replyLinks.find((item, index) => {
        if (item.nativeElement.id === anchorID) {
          if (this.object.description) {
            if (index === 0) {
              // first link in this case is the description, not a reply
              this.descriptionExpanded = true;
            } else {
              // expand this reply
              this.expandedRepliesByIndex[index - 1] = true;
            }
          } else {
            // expand this reply
            this.expandedRepliesByIndex[index] = true;
          }
          return true;
        } else {
          return false;
        }
      });
      const headerOffset = this.topOffset + 80;
      const elementPosition = replyLink.nativeElement.getBoundingClientRect().top;
      const offsetPosition = elementPosition + window.scrollY - headerOffset;

      window.scrollTo({
        top: offsetPosition,
        behavior: 'smooth'
      });
    }, 0);
  }

  private getTimestampDisplay(datetime, currentMoment) {
    const createdAt = new Date(datetime);
    // @ts-ignore
    const diff = currentMoment - createdAt;
    return _moment.duration(-diff).locale(this.config.locale).humanize(true);
  }

  openInsertPredefinedReplyDialog() {
    return this.matDialog.open(
      InsertPredefinedReplyDialogComponent, {}).afterClosed().subscribe(result => {
      if (!result) {
        return;
      }
      this.tinyMceArea.editor.execCommand('mceInsertContent', false, result);
      return {message: result} as IActionResult;
    });
  }

  clickedDescription() {
    if (!this.descriptionExpanded) {
      this.descriptionExpanded = !this.descriptionExpanded
    }
  }

  clickedReply(index) {
    if (!this.expandedRepliesByIndex[index]) {
      this.expandedRepliesByIndex[index] = !this.expandedRepliesByIndex[index]
    }
  }

  handleScrollPosition() {
    this.fragmentSubscription = this.activatedRoute.fragment.subscribe(fragmentValue => {
      if (fragmentValue && fragmentValue !== 'details') {
        if (fragmentValue !== this.currentFragment) {
          this.currentFragment = fragmentValue;

          // before going to anchor on page, make sure all images are loaded
          const images = document.querySelectorAll('.ticket-message img');
          if (images && images.length) {
            let loadedImages = 0;
            const listener = () => {
              loadedImages += 1;
              if (images.length === loadedImages) {
                this.goToAnchor(this.currentFragment);
              }
            };
            images.forEach(image => {
              image.addEventListener('load', listener);
              image.addEventListener('error', listener);
            });
          } else {
            // no images on page, we can go directly to the anchor
            this.goToAnchor(this.currentFragment);
          }
        }
      } else {
        setTimeout(() => {
          this.scrollToTextarea();
        });
      }
    });
  }

  ngAfterViewInit() {
    super.ngAfterViewInit();
    this.tinyMceHelper.manageUndo(this.replyForm.controls.replyText, this.tinyMceArea);
    this.handleScrollPosition();
  }

  switchCollapseAll() {
    if (this.allExpanded) {
      // collapse all except last one
      this.descriptionExpanded = false;
      this.replyMessageContainer.forEach((item, index) => {
        if (index !== this.replyMessageContainer.length - 1) {
          this.expandedRepliesByIndex[index] = false;
        }
      });
      this.allExpanded = false;
    } else {
      // expand all
      this.descriptionExpanded = true;
      this.replyMessageContainer.forEach((item, index) => {
        this.expandedRepliesByIndex[index] = true;
      });
      this.allExpanded = true;
    }
  }

  handleTinyKeyDown(event) {
    if (event && event.event && event.event.ctrlKey && event.event.keyCode === 13) {
      event.event.preventDefault();
      this.replyToTicketActions(this.primaryAction);
    }
  }

  ngOnDestroy() {
    super.ngOnDestroy();
    this.tinyMceHelper.removeSubscriptionForUndo();
    if (this.fragmentSubscription) {
      this.fragmentSubscription.unsubscribe();
      this.fragmentSubscription = null;
    }
    if (this.topOffsetSubscription) {
      this.topOffsetSubscription.unsubscribe();
      this.topOffsetSubscription = null;
    }
    if (this.routerSubscription) {
      this.routerSubscription.unsubscribe();
      this.routerSubscription = null;
    }
  }

  private formatHtml() {
    this.ticketDescription = this.domSanitizer.bypassSecurityTrustHtml(this.object.description);
    let index = 0;
    for (let item of this.object.replies_and_notes) {
      if (item.message) {
        item.htmlMessage = this.domSanitizer.bypassSecurityTrustHtml(item.message);
      }
      if (!this.expandedRepliesByIndex.hasOwnProperty(index.toString())) {
        if (this.config.current.settings.collapsedTicketRepliesFeature) {
          if (index !== this.object.replies_and_notes.length - 1) {
            this.expandedRepliesByIndex[index] = false;
          } else {
            this.expandedRepliesByIndex[index] = true;
          }
        } else {
          this.expandedRepliesByIndex[index] = true;
        }
      }
      index += 1;
    }
  }
}
