import type { IDAgent } from '../agent/index.js';

import { CID } from 'multiformats';
import { Encoder, Encryption } from '@dwn-protocol/id';

import bs58 from 'bs58';
import IPFS from 'ipfs-infura';

import { getServiceDwnEndpoints } from '../service-options.js';
import _ from 'lodash';

export class Metadata {
  private agent: IDAgent;
  private connectedDid: string;
  private _ipfs: IPFS;

  constructor(options: { agent: IDAgent, connectedDid: string }) {
    this.agent = options.agent;
    this.connectedDid = options.connectedDid;
  }

  async config() {
    const h = '1220' + '0x32216e417b6f98f95febedf6a747c5020ea95558fbebd98ba98a155791b0b6d2'.slice(2);
    const b = Buffer.from(h, 'hex');
    const c = bs58.encode(b);
    const r = await fetch(`https://dwn.infura-ipfs.io/ipfs/${CID.parse(c).toV1().toString()}`);
    return JSON.parse(bs58.decode(await r.text()).toString());
  }

  async aliasGet(alias) {
    const relayer = _.sample(await getServiceDwnEndpoints());
    const response = await fetch(`${relayer}/did/${alias}`, {
      method  : 'GET',
      mode    : 'cors',
      cache   : 'no-cache',
      headers : {
        'Accept'       : '*/*',
        'Content-Type' : 'application/json',
      },
    });

    return await response.text();
  }

  async aliasSet(alias, did, metadata = { VerifiableCredentials: [] }) {
    const relayer = _.sample(await getServiceDwnEndpoints());
    const response = await fetch(`${relayer}/did`, {
      method  : 'POST',
      mode    : 'cors',
      cache   : 'no-cache',
      headers : {
        'Accept'       : '*/*',
        'Content-Type' : 'application/json',
      },
      body: JSON.stringify({
        alias,
        did,
        metadata,
      }),
    });

    return await response.text();
  }

  async aliasPut(alias, did, metadata = { VerifiableCredentials: [] }) {
    const relayer = _.sample(await getServiceDwnEndpoints());
    const response = await fetch(`${relayer}/did`, {
      method  : 'PUT',
      mode    : 'cors',
      cache   : 'no-cache',
      headers : {
        'Accept'       : '*/*',
        'Content-Type' : 'application/json',
      },
      body: JSON.stringify({
        alias,
        did,
        metadata,
      }),
    });

    return await response.text();
  }

  async save(data) {
    try {
      const b = Buffer.from(data);
      const d = bs58.encode(b);
      this._ipfs = new IPFS(await this.config());
      const cid = await this._ipfs.add(d);
      this._ipfs = undefined;
      return this.getBytes32FromIpfsHash(cid.toString());
    } catch (e) {
      throw new Error('Failure to submit file to IPFS');
    }
  }

  async get(id) {
    try {
      const cid = this.getIpfsHashFromBytes32(id);
      const response = await fetch(`https://dwn.infura-ipfs.io/ipfs/${CID.parse(cid).toV1().toString()}`);
      const text = await response.text();
      return bs58.decode(text).toString();
    } catch (e) {
      throw new Error('Failure to get file from IPFS');
    }
  }

  async saveJson(jsonData) {
    try {
      this._ipfs = new IPFS(await this.config());
      const cid = await this._ipfs.addJSON(jsonData);
      this._ipfs = undefined;
      return this.getBytes32FromIpfsHash(cid.toString());
    } catch (e) {
      throw new Error('Failure to submit file to IPFS');
    }
  }

  async getJson(id) {
    try {
      const cid = this.getIpfsHashFromBytes32(id);
      const response = await fetch(`https://dwn.infura-ipfs.io/ipfs/${CID.parse(cid).toV1().toString()}`);
      const json = await response.json();
      return json;
    } catch (e) {
      throw new Error('Failure to get file from IPFS');
    }
  }

  async encrypt(publicKey, input) {
    let encryptionOutput = await Encryption.eciesSecp256k1Encrypt(publicKey, input);
    return Buffer.from(Encoder.bytesToString(Encoder.objectToBytes(encryptionOutput))).toString('base64');
  }

  async decrypt(privateKey, output) {
    let newOutput = {};
    let json = Buffer.from(output, 'base64').toString('ascii');
    Object.entries(JSON.parse(json)).forEach((entry) => {
      const [key, value] = entry;
      //@ts-ignore
      newOutput[key] = value.type == 'Buffer' ? Buffer.from(value.data) : value;
    });
    const decryptionInput = { privateKey, ...newOutput };
    //@ts-ignore
    const decryptedPlaintext = await Encryption.eciesSecp256k1Decrypt(decryptionInput);
    return new TextDecoder().decode(decryptedPlaintext);
  }

  private getBytes32FromIpfsHash(ipfsHash) {
    return (
      '0x' + bs58.decode(ipfsHash).slice(2).toString('hex')
    );
  }

  private getIpfsHashFromBytes32(bytes32Hex) {
    const hashHex = '1220' + bytes32Hex.slice(2);
    const hashBytes = Buffer.from(hashHex, 'hex');
    const hashStr = bs58.encode(hashBytes);
    return hashStr;
  }

}