import { Injectable } from "@angular/core";
import {
  roomMessagesQuery,
  dialogueMessagesQuery,
  createDialogueMessageQuery,
  deleteDialogueMessageQuery,
  editDialogueMessageQuery,
  seenDialogueMessageQuery,
  getDialogueDataQuery,
  editDialoguePlacementQuery,
  createDialogueQuery,
} from "../server-services/querys";
import { ServerRestApiService } from "../services/server-rest-api.service";
import { AccountService } from "./account.service";
import { PeerAccountKeyring } from "../crypto/keyring/account_peer";
import { AuthService } from "./auth.service";
import { DIALOGUE_CRYPTO_CONTEXT } from "../crypto/context/dialogue/__init__";
import { getTimeFromId } from "../crypto/context/timestamp";
import msgpack from "msgpack-lite";
import {
  DialogueConfigDumpArgs,
  DialogueConfigLoadArgs,
} from "../crypto/context/dialogue/args";
import { ResourceBase } from "./resource-base";
import {
  DialogueDataRecord,
  MessageRecord,
} from "./query-records/room-records";
import { ID } from "./query-records/common-records";
import { marker } from "@biesbjerg/ngx-translate-extract-marker";

@Injectable({
  providedIn: "root",
})
export class DialogueService implements ResourceBase {
  /**
   * Map<resourceId, event handlers>
   * This collection is wrap all of the subscription handler for a specific resource (key)
   * We separate the CMD+Typing callbacks
   * You can use the subscription to unsubscribe an apollo subscription
   */

  private peerKeyrings = {};

  constructor(
    private serverRestApiService: ServerRestApiService,
    private accountService: AccountService,
    private authService: AuthService
  ) {}

  public getKeyring(resourceId): Promise<PeerAccountKeyring> {
    if (this.peerKeyrings[resourceId]) {
      return Promise.resolve(this.peerKeyrings[resourceId]);
    } else {
      return this.accountService.getPeerKeyring(resourceId).then((peer_kr) => {
        this.peerKeyrings[resourceId] = peer_kr;
        return peer_kr;
      });
    }
  }

  // direct api-calls --------------------------------------

  /**
   * @param id
   * @returns
   */
  public getData(id, pinnedKey?: Uint8Array): Promise<DialogueDataRecord> {
    return this.serverRestApiService.query({
      query: getDialogueDataQuery,
      variables: {
        id,
      },
    });
  }

  public createDialogue(peerId: ID): Promise<void>{
    return new Promise((resolve, reject)=>{
      this.serverRestApiService.mutate({
        query: createDialogueQuery,
        variables: {
          peerId
        }
      }).then((res)=>{
        // this generate a dialogue, but it comes only via the subscription handler
        // we have to wait, while this merges into the cache, because the "getDialogue"
        // what will be called after this is an offline query
        let attempt = 0;

        let checkForDialogue = ()=>{
          this.getData(peerId).then(()=>{
            resolve();
          }).catch(()=>{
            if (attempt < 30){
              attempt++;
              setTimeout(checkForDialogue, 100);
            }
            else{
              console.error('the dialogue is not loaded into the cache', peerId)
              reject();
            }
          })
        }
        checkForDialogue();
      }).catch(reject);
    })
    
  }

  private decryptMessage(data, variables): Promise<Object> {
    console.log("load", data, variables);

    let messages = data;

    let promises: Promise<void>[] = [];

    messages &&
      messages.forEach((msg) => {
        promises.push(
          this.getKeyring(variables["peerId"]).then((peer_kr) => {
            return DIALOGUE_CRYPTO_CONTEXT.load(
              new DialogueConfigLoadArgs(
                msg["content"],
                this.authService.getSelfAccountKeyring(),
                peer_kr,
                getTimeFromId(msg["id"])
              )
            )
              .then((loadedData) => {
                msg["content"] = msgpack.decode(loadedData)[1];
                return;
              })
              .catch(() => {
                msg["error"] = true;
                msg["errorMessage"] = marker("Error: Can not decrypt message")
                return;
              });
          })
        );
      });

    return Promise.all(promises).then(() => {
      return data;
    });
  }

  /**
   * Note: Be sure, you have already called the getRoomData - need for creating the room_kr
   */
  public getLastMessages(resourceId): Promise<MessageRecord[]> {
    return this.serverRestApiService
      .query({
        query: dialogueMessagesQuery,
        variables: {
          peerId: resourceId,
          pivot: null,
          after: false,
          count: 20,
          prepareSub: true,
        },
        decrypt: this.decryptMessage.bind(this),
      })
      .then((data) => {
        console.log("diadata", data);
        return data;
      });
  }

  public getMoreMessages(
    resourceId,
    cursorMessageId
  ): Promise<MessageRecord[]> {
    return this.serverRestApiService.query({
      query: dialogueMessagesQuery,
      variables: {
        peerId: resourceId,
        pivot: cursorMessageId,
        after: false,
        count: 20,
        prepareSub: false,
      },
      decrypt: this.decryptMessage.bind(this),
    });
  }

  public sendMessage(resourceId, content): Promise<string> {
    return this.serverRestApiService.mutate({
      query: createDialogueMessageQuery,
      variables: {
        peerId: resourceId,
        content,
      },
      encrypt: this.encryptMessage.bind(this),
    });
  }

  private encryptMessage(data): Promise<Object> {
    return this.getKeyring(data["peerId"]).then((peer_kr) => {
      return DIALOGUE_CRYPTO_CONTEXT.dump(
        new DialogueConfigDumpArgs(
          msgpack.encode([1, data["content"]]),
          this.authService.getSelfAccountKeyring(),
          peer_kr,
          data.messageId?getTimeFromId(data.messageId):this.serverRestApiService.makeTimestamp()
        )
      ).then((content) => {
        data["content"] = content;
        return data;
      });
    });
  }

  public deleteMessage(resourceId, messageId): Promise<any> {
    return this.serverRestApiService.mutate({
      query: deleteDialogueMessageQuery,
      variables: {
        peerId: resourceId,
        messageId,
      },
    });
  }

  public editMessage(resourceId, messageId, content): Promise<any> {
    return this.serverRestApiService.mutate({
      query: editDialogueMessageQuery,
      variables: {
        peerId: resourceId,
        messageId,
        content,
      },
      encrypt: this.encryptMessage.bind(this),
    });
  }

  public seenMessage(resourceId, messageId): Promise<any> {
    return this.serverRestApiService.mutate({
      query: seenDialogueMessageQuery,
      variables: {
        peerId: resourceId,
        seenId: messageId,
      },
    });
  }

  public editPlacement(id, groupId, ordering: number) {
    return this.serverRestApiService.mutate({
      query: editDialoguePlacementQuery,
      variables: {
        peerId: id,
        ordering,
        groupId,
      },
    });
  }
}
