import {
  Command,
  CommandConfig,
  CommandConfigParameter,
  CommandParameters,
} from '@makeflow/gateway-shared';
import {action, computed, observable, runInAction} from 'mobx';

import {APIService} from './api-service';

const COMMANDS_LENGTH_TO_FETCH = 20;

export interface FetchCommandsHistoryOptions {
  before?: number;
  after?: number;
  limit?: number;
}

export class CommandService {
  @observable
  private _configURL: string | undefined;

  @observable
  private _commandHistoryItemMap = new Map<number, Command>();

  @observable
  private _commands: CommandConfig[] = [];

  @observable
  private _fetchedAllHistory = false;

  @computed
  get configURL(): string | undefined {
    return this._configURL;
  }

  @computed
  get fetchedAllHistory(): boolean {
    return this._fetchedAllHistory;
  }

  @computed
  get commandHistoryItems(): Command[] {
    return Array.from(this._commandHistoryItemMap.values()).sort(
      (a, b) => b.createdAt - a.createdAt,
    );
  }

  @computed
  get commands(): CommandConfig[] {
    return this._commands;
  }

  @computed
  get firstCommandId(): number | undefined {
    return Array.from(this._commandHistoryItemMap.keys()).sort()[0];
  }

  @computed
  get lastCommandId(): number | undefined {
    return Array.from(this._commandHistoryItemMap.keys()).sort(
      (a, b) => b - a,
    )[0];
  }

  constructor(private apiService: APIService) {}

  async fetchMoreCommandsHistory(): Promise<void> {
    let commands = await this.fetchCommandsHistory({
      before: this.firstCommandId,
      limit: COMMANDS_LENGTH_TO_FETCH,
    });

    if (commands.length < COMMANDS_LENGTH_TO_FETCH) {
      runInAction(() => {
        this._fetchedAllHistory = true;
      });
    }

    this.pushCommandsHistory(commands);
  }

  async fetchLatestCommandsHistory(): Promise<void> {
    if (!this.commandHistoryItems.length) {
      await this.fetchMoreCommandsHistory();
      return;
    }

    let commands = await this.fetchCommandsHistory({
      after: this.lastCommandId,
    });

    this.pushCommandsHistory(commands);
  }

  async fetchCommandHistoryItem(id: number): Promise<void> {
    let commands = await this.fetchCommandsHistory({before: id + 1, limit: 1});

    this.pushCommandsHistory(commands);
  }

  getCommandsHistoryItem(id: number): Command | undefined {
    return this.commandHistoryItems.find(item => item.id === id);
  }

  async fetchCommands(): Promise<void> {
    let commands = await this.apiService.post<CommandConfig[]>(
      '/api/commands/get-commands',
    );

    this.setCommands(commands);
  }

  async fetchCommandsConfigURL(): Promise<void> {
    let url = await this.apiService.post<string>(
      '/api/commands/get-commands-config-url',
    );

    this.setConfigURL(url);
  }

  async updateCommands(): Promise<void> {
    let commands = await this.apiService.post<CommandConfig[]>(
      '/api/commands/update-commands',
    );

    this.setCommands(commands);
  }

  async updateCommandsConfigURL(url: string): Promise<void> {
    let commands = await this.apiService.post<CommandConfig[]>(
      '/api/commands/update-commands-config-url',
      {
        url,
      },
    );

    this.setConfigURL(url);
    this.setCommands(commands);
  }

  async fetchCommandsHistory(
    options: FetchCommandsHistoryOptions,
  ): Promise<Command[]> {
    return this.apiService.post('/api/commands/get-commands-history', options);
  }

  async fetchAllNotExecutedCommandsHistory(): Promise<void> {
    let commands = await this.apiService.post<Command[]>(
      '/api/get-all-not-executed-commands-history',
    );

    this.pushCommandsHistory(commands);
  }

  async executeCommandForQueue(id: number): Promise<void> {
    await this.apiService.post(`/api/commands/queue/${id}/execute`);
  }

  async executeCommand(
    command: CommandConfig,
    parameters: CommandParameters,
  ): Promise<void> {
    await this.apiService.post(`/api/command/${command.name}/execute`, {
      parameters,
    });
  }

  @action
  private pushCommandsHistory(commands: Command[]): void {
    for (let command of commands) {
      this._commandHistoryItemMap.set(command.id, command);
    }
  }

  @action
  private setCommands(commands: CommandConfig[]): void {
    this._commands = commands;
  }

  @action
  private setConfigURL(url: string): void {
    this._configURL = url;
  }
}
