import { ChangeDetectorRef, Component, Input, NgZone, OnDestroy, OnInit } from '@angular/core';
import { TaskLogApiService } from '@fleio-api/utils/task-log/task-log-api.service';
import { ConfigService } from '@shared/config/config.service';
import { RefreshTimer } from '@shared/ui-api/helpers/refresh-timer';
import { LineDirection } from '@objects-view/status-line/status-line.component';
import { of, Subscription } from 'rxjs';
import { CallbackAction } from '@objects-view/actions/callback-action';
import { ApiCallAction, CallType } from '@objects-view/actions/api-call-action';
import { ITaskModel } from '@fleio-api/utils/model/task.model';
import * as _moment from 'moment';
import { IObjectStatus, StatusType, StatusValue } from '@objects-view/interfaces/object-status';
import { IActivityLogModel } from '@fleio-api/utils/model/activity-log.model';
import { BaseAction } from '@objects-view/actions/base-action';

interface ITaskDetails {
  task: ITaskModel;
  status: IObjectStatus;
  taskLogLoaded: boolean;
  taskLogVisible: boolean;
  taskFinished: boolean;
  taskLog?: string;
  actions: BaseAction[];
  tooltip: string;
  subtext: string;
  depth: number;
  has_subtasks: boolean;
  visible?: boolean
}

@Component({
  selector: 'app-activity-log-tasks',
  templateUrl: './activity-log-tasks.component.html',
  styleUrls: ['./activity-log-tasks.component.scss']
})
export class ActivityLogTasksComponent implements OnInit, OnDestroy {
  @Input() activityLog: IActivityLogModel;

  constructor(
    private taskLogApi: TaskLogApiService,
    public config: ConfigService,
    private logDetailsZone: NgZone,
    private changeDetectorRef: ChangeDetectorRef,
  ) {
  }

  tasksDetails: ITaskDetails[] = [];
  taskDetailsRefreshTimers: Record<string, RefreshTimer> = {};
  unfinishedTaskStates = ['started', 'pending', 'retry'];

  statusLineDirection = LineDirection.Vertical;
  appBreakpointSubscription: Subscription;
  appBreakpoint: string;

  taskTrackBy(index: number, item: ITaskDetails): any {
    return item.task.id;
  }

  loadTasksDetails() {
    if (this.activityLog && this.activityLog.tasks_count > 0) {
      this.taskLogApi.getTasksForActivity(this.activityLog.id).subscribe(tasks => {
        const oldTasksDetails = this.tasksDetails;
        // Do not remove already loaded tasks from the list if they have a refresh timer initialized as
        // they will be updated by the related refresh timer. If not done this way, expanded & unfinished task may
        // not load the latest lines in the log file.
        this.tasksDetails = this.tasksDetails.filter(taskDetails => (
          this.taskDetailsRefreshTimers.hasOwnProperty(taskDetails.task.id) &&
          this.taskDetailsRefreshTimers[taskDetails.task.id]
        ));

        for (const taskIndex of Object.keys(tasks)) {
          const task = tasks[taskIndex];
          if (this.tasksDetails.find(taskDetails => taskDetails.task.id === task.id)) {
            // when reloading data, some tasks may have not been removed from this list because
            // of already being refreshed by a refresh timer (see above), skip them
            continue;
          }
          const oldTaskDetails = oldTasksDetails?.find(t => t.task.id === task.id);
          let subtext: string = '';

          if (task.operations.length >= 1) {
            subtext = (task.operations.length == 1) ? $localize`1 operation` : $localize`${task.operations.length} operations`;
          }

          if (task.subtasks_count >= 1) {
            if (subtext) {
              subtext += ` ,`;
            }
            subtext += (task.subtasks_count == 1) ? $localize`1 task` : $localize`${task.subtasks_count} tasks`;
          }

          this.tasksDetails.push({
            task,
            status: this.getTaskDetailsStatus(task.state),
            taskLog: oldTaskDetails ? oldTaskDetails.taskLog : null,
            taskLogLoaded: oldTaskDetails ? oldTaskDetails.taskLogLoaded : false,
            taskLogVisible: oldTaskDetails ? oldTaskDetails.taskLogVisible : false,
            taskFinished: !this.unfinishedTaskStates.includes(task.state),
            tooltip: this.getTooltip(task),
            subtext: subtext,
            has_subtasks: task.has_subtasks,
            depth: task.depth,
            actions: [
              new CallbackAction({
                icon: {name: 'open_in_new'},
                tooltip: $localize`Open in new tab`,
                name: $localize`Open in new tab`,
                callback: () => {
                  const popup = window.open('');
                  this.config.authService.generateOtpToken().subscribe(token => {
                    popup.location.href = `${this.config.current.urls.backendApiUrl}tasklog/${task.id}/load_task_log?fleio-token=${token}`;
                  });
                  return of(null);
                }
              }),
              new ApiCallAction({
                icon: {name: 'reboot', class: 'fl-icons'},
                tooltip: $localize`Recreate task`,
                name: $localize`Recreate task and run again`,
                confirmOptions: {
                  confirm: true,
                  title: $localize`Recreate task and run again`,
                  message: $localize`Are you sure you want to Recreate task ${task.id} and run again?`,
                },
                object: task,
                apiService: this.taskLogApi,
                callType: CallType.Post,
                apiAction: 'recreate',
                apiParams: {},
              }),
            ]
          });
        }

        // top level tasks are always visible
        for (const taskDetails of this.tasksDetails) {
          taskDetails.visible = taskDetails.depth === 1;
        }
      });
    }
  }

  ngOnInit() {
    this.loadTasksDetails();
    if (this.config?.appBreakpointObservable) {
      this.appBreakpointSubscription = this.config.appBreakpointObservable.subscribe(breakpoint => {
        this.appBreakpoint = breakpoint;
      });
    }
  }

  dataChanged() {
    this.loadTasksDetails();
  }

  getTooltip(task: ITaskModel): string {
    const executionDuration = _moment.duration(
      // @ts-ignore
      new Date(task.last_update) - new Date(task.created_at)
    ).locale(this.config.locale).humanize();

    const runningSince = _moment.duration(
      // @ts-ignore
      new Date() - new Date(task.created_at)
    ).locale(this.config.locale).humanize();


    switch (task.state) {
      case 'pending':
      case 'started':
      case 'retry':
        return $localize`Running since ${runningSince} ago`;
      case 'success':
        return $localize`Successfully executed in ${executionDuration}`;
      case 'failure':
        return $localize`Failed after ${executionDuration}`;
    }
  }

  private getTaskDetailsStatus(taskState: string) {
    let taskStatus = {
      type: StatusType.Defined,
      value: StatusValue.Undefined,
    } as IObjectStatus;
    switch (taskState) {
      case 'pending':
        taskStatus = {
          type: StatusType.Changing,
          value: StatusValue.Pending,
        };
        break;
      case 'started':
        taskStatus = {
          type: StatusType.Changing,
          value: StatusValue.Started,
        };
        break;
      case 'retry':
        taskStatus = {
          type: StatusType.Changing,
          value: StatusValue.Pending,
        };
        break;
      case 'failure':
        taskStatus = {
          type: StatusType.Defined,
          value: StatusValue.Failed,
        };
        break;
      case 'success':
        taskStatus = {
          type: StatusType.Defined,
          value: StatusValue.Success,
        };
        break;
    }
    return taskStatus;
  }

  private stopTaskDetailsRefreshTimer(taskDetails: ITaskDetails) {
    if (this.taskDetailsRefreshTimers.hasOwnProperty(taskDetails.task.id)) {
      const refreshTimerInstance = this.taskDetailsRefreshTimers[taskDetails.task.id];
      if (refreshTimerInstance && refreshTimerInstance.isStarted) {
        this.taskDetailsRefreshTimers[taskDetails.task.id].stop();
        this.taskDetailsRefreshTimers[taskDetails.task.id] = null;
      }
    }
  }

  private requestTaskLog(taskDetails: ITaskDetails, callback = null) {
    this.taskLogApi.getTaskLog(taskDetails.task.id).subscribe(taskLogResponse => {
      this.updateTaskDetails(taskDetails, taskLogResponse);
      if (callback) {
        callback(taskDetails);
      }
    });
  }

  private updateTaskDetails(taskDetails: ITaskDetails, taskLogResponse) {
    taskDetails.taskLog = taskLogResponse.task_log;
    taskDetails.taskLogLoaded = true;
    taskDetails.task.state = taskLogResponse.task_state;
    // update status line
    taskDetails.status = this.getTaskDetailsStatus(taskDetails.task.state);
    if (!taskDetails.taskFinished) {
      this.scrollToBottomOfLog(taskDetails);
    }
    taskDetails.taskFinished = !this.unfinishedTaskStates.includes(taskDetails.task.state);
  }

  scrollToBottomOfLog(taskDetails: ITaskDetails, forceScroll = false) {
    if (taskDetails) {
      if (forceScroll) {
        // scroll regardless of current scroll position
        this.changeDetectorRef.detectChanges();
        const taskContainer = document.getElementById(`container-${taskDetails.task.id}`);
        taskContainer.scrollTop = taskContainer.scrollHeight;
      } else {
        // continue to scroll loaded content only if user already has the scroll position at the bottom
        const taskContainer = document.getElementById(`container-${taskDetails.task.id}`);
        if (taskContainer) {
          const shouldScrollDown = (
            taskContainer.scrollHeight - taskContainer.scrollTop - taskContainer.clientHeight
          ) < 1;
          this.changeDetectorRef.detectChanges();
          if (shouldScrollDown) {
            taskContainer.scrollTop = taskContainer.scrollHeight;
          }
        }
      }
    }
  }

  updateChildrenVisibleStatus(taskDetails: ITaskDetails, visible: boolean) {
    for (const childTaskDetails of this.tasksDetails.filter(t => t.task.parent_id === taskDetails.task.id)) {
      childTaskDetails.visible = visible;
      if (!visible) {
        childTaskDetails.taskLogVisible = false;
        this.updateChildrenVisibleStatus(childTaskDetails, false);
      }
    }
  }

  toggleTaskLog(taskDetails: ITaskDetails, event: MouseEvent | PointerEvent) {
    const buttonTagName = 'BUTTON';
    if (!!event.composedPath().find((element: HTMLElement) => element.tagName === buttonTagName)) {
      // do not toggle task log if an action button was pressed
      return;
    }
    taskDetails.taskLogVisible = !taskDetails.taskLogVisible;
    this.updateChildrenVisibleStatus(taskDetails, taskDetails.taskLogVisible);

    if (taskDetails.taskLogVisible) {
      if (!taskDetails.taskFinished) {
        // if not final state, pass a callback where we start a refresh timer
        this.requestTaskLog(taskDetails, () => {
            this.scrollToBottomOfLog(taskDetails, true);
            this.taskDetailsRefreshTimers[taskDetails.task.id] = new RefreshTimer(
              this.config.current.settings.refreshIntervals.defaultIntervalShort, () => {
                this.taskLogApi.getTaskLog(taskDetails.task.id).subscribe(taskLogResponse => {
                  this.updateTaskDetails(taskDetails, taskLogResponse);
                  if (taskDetails.taskFinished) {
                    // task is finished, we can stop related refresh timer
                    this.stopTaskDetailsRefreshTimer(taskDetails);
                  }
                });
              }, this.config, this.logDetailsZone, this.changeDetectorRef, true
            );
          }
        );
      } else {
        this.requestTaskLog(taskDetails);
      }
    } else {
      // task details hidden, make sure refresh timer for this one is stopped
      this.stopTaskDetailsRefreshTimer(taskDetails);
    }
  }

  ngOnDestroy() {
    if (this.appBreakpointSubscription) {
      this.appBreakpointSubscription.unsubscribe();
      this.appBreakpointSubscription = null;
    }
  }
}
