import EC from "elliptic";
import SHA256 from "crypto-js/sha256.js";

const ec = new EC.ec("secp256k1");

type MergeTransformationOrigin = {
  target: number,
  originSignature: string,
  vol: number,
  height: number,
  isSplit?: boolean
}

type MergeTransformationTarget = {
  origin: number,
  vol: number,
  height: number
}

type Centract = {
  centractSignature: string
  transactionSignature: string
  transactionAddress: string
  code: string
}

type Transaction = {
  holder: string,
  transactionSignature: string,
  transformationType?: "merge"
  transformation?: MergeTransformationOrigin | MergeTransformationTarget
  centract?: Centract
}

type Coin = {
  val: number, // the value of the coin (what amount of 1 coin is it worth)
  genesisTime: number,
  paidFee?: boolean,
  locked?: boolean,
  seed?: string,
  diff?: string,
  hash?: string,
  transactions: Transaction[] // the chain-of-ownership list
}

class CentrixClient {
  server: string;

  constructor(server = "https://clc.ix.tc") {
    this.server = server;
  }

  randomKey() {
    return Array.from({ length: 32 })
    .map(() => Math.floor(Math.random() * 256).toString(16).padStart(2, '0'))
    .join('');
  }

  publicKey(priv: string) {
    return ec.keyFromPrivate(priv).getPublic().encode("hex", false);
  }

  async transact(priv: string, cid: number, addr: string): Promise<null | string> {
    const kp = ec.keyFromPrivate(priv);
    const sign = kp.sign(SHA256(addr).toString()).toDER("hex");

    const res = await fetch(this.server + `/transaction?cid=${cid}&newholder=${addr}&sign=${sign}`)
    
    if (!res.ok && res.status !== 500) return `Could not transact coin ${cid} (failed to fetch, status code: ${res.status})`;

    const data = await res.json();

    if (data.error) return `Could not transact coin ${cid} (Centrix server (${this.server}) responded with error: ${data.error})`;

    return null;
  }

  async getCoin(cid: number): Promise<Coin | string> {
    const res = await fetch(this.server + `/coin/${cid}`)
    
    if (!res.ok && res.status !== 500) return `Could not fertch coin ${cid} (failed to fetch, status code: ${res.status})`;

    const data = await res.json();

    if (data.error) return `Could not fetch coin ${cid} (Centrix server (${this.server}) responded with error: ${data.error})`;

    return data.coin as Coin;
  }

  async merge(origin: number, target: number, vol: number, priv: string): Promise<null | string> {
    const originCoin = await this.getCoin(origin);
    const targetCoin = await this.getCoin(target);
    if (typeof originCoin === "string") return originCoin;
    if (typeof targetCoin === "string") return targetCoin;

    const kp = ec.keyFromPrivate(priv);
    const sign = ec.sign(SHA256(target + " " + targetCoin.transactions.length + " " + vol).toString(), kp).toDER("hex");

    const res = await fetch(this.server + `/merge?origin=${origin}&target=${target}&sign=${sign}&vol=${vol}`);
    if (!res.ok && res.status !== 500) return `Could not merge coin ${origin} into coin ${target} (failed to fetch, status code: ${res.status})`;
    const data = await res.json();
    if (data.error) return `Could not merge coin ${origin} into coin ${target} (Centrix server (${this.server}) responded with error: ${data.error})`;

    return null;
  }

  async split(origin: number, vol: number, priv: string): Promise<number | string> {
    const originCoin = await this.getCoin(origin);
    if (typeof originCoin === "string") return originCoin;

    const kp = ec.keyFromPrivate(priv);
    const sign = ec.sign(SHA256("split " + originCoin.transactions.length + " 0 " + vol).toString(), kp).toDER("hex");

    const res = await fetch(this.server + `/split?origin=${origin}&sign=${sign}&vol=${vol}`);
    if (!res.ok && res.status !== 500) return `Could not split coin ${origin} (failed to fetch, status code: ${res.status})`;
    const data = await res.json();
    if (data.error) return `Could not split coin ${origin} (Centrix server (${this.server}) responded with error: ${data.error})`;

    return data.id;
  }
}

export default CentrixClient;
export { MergeTransformationOrigin, MergeTransformationTarget, Centract, Transaction, Coin };