import * as R from "ramda";
import { IIdentifier } from "@veramo/core";
import { accessToken, SimpleSigner } from "@/libs/crypto";
import * as didJwt from "did-jwt";
import { Axios, AxiosResponse } from "axios";
import { Contact } from "@/db/tables/contacts";

export const SCHEMA_ORG_CONTEXT = "https://schema.org";
export const SERVICE_ID = "endorser.ch";

export interface AgreeVerifiableCredential {
  "@context": string;
  "@type": string;
  // "any" because arbitrary objects can be subject of agreement
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  object: Record<any, any>;
}

export interface GiverInputInfo {
  did?: string;
  name?: string;
}

export interface ClaimResult {
  success: { claimId: string; handleId: string };
  error: { code: string; message: string };
}

export interface GenericClaim {
  "@context": string;
  "@type": string;
  issuedAt: string;
  // "any" because arbitrary objects can be subject of agreement
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  claim: Record<any, any>;
}

export interface GiveServerRecord {
  agentDid: string;
  amount: number;
  amountConfirmed: number;
  description: string;
  fullClaim: GiveVerifiableCredential;
  handleId: string;
  issuedAt: string;
  recipientDid: string;
  unit: string;
}

export interface GiveVerifiableCredential {
  "@context"?: string; // optional when embedded, eg. in an Agree
  "@type": string;
  agent?: { identifier: string };
  description?: string;
  fulfills?: { "@type": string; identifier: string };
  identifier?: string;
  object?: { amountOfThisGood: number; unitCode: string };
  recipient: { identifier: string };
}

export interface RegisterVerifiableCredential {
  "@context": string;
  "@type": string;
  agent: { identifier: string };
  object: string;
  recipient: { identifier: string };
}

export interface InternalError {
  error: string; // for system logging
  userMessage?: string; // for user display
}

// This is used to check for hidden info.
// See https://github.com/trentlarson/endorser-ch/blob/0cb626f803028e7d9c67f095858a9fc8542e3dbd/server/api/services/util.js#L6
const HIDDEN_DID = "did:none:HIDDEN";

export function isHiddenDid(did) {
  return did === HIDDEN_DID;
}

/**
 always returns text, maybe UNNAMED_VISIBLE or UNKNOWN_ENTITY
 **/
export function didInfo(did, activeDid, identifiers, contacts) {
  const myId: IIdentifier | undefined = R.find(
    (i) => i.did === did,
    identifiers,
  );
  if (myId) {
    return "You" + (myId.did !== activeDid ? " (Alt ID)" : "");
  } else {
    const contact: Contact | undefined = R.find((c) => c.did === did, contacts);
    if (contact) {
      return contact.name || "Someone Unnamed in Contacts";
    } else if (!did) {
      return "Unspecified Person";
    } else if (isHiddenDid(did)) {
      return "Someone Not In Network";
    } else {
      return "Someone Not In Contacts";
    }
  }
}

/**
 * For result, see https://endorser.ch:3000/api-docs/#/claims/post_api_v2_claim
 *
 * @param identity
 * @param fromDid may be null
 * @param toDid
 * @param description may be null; should have this or hours
 * @param hours may be null; should have this or description
 */
export async function createAndSubmitGive(
  axios: Axios,
  apiServer: string,
  identity: IIdentifier,
  fromDid: string,
  toDid: string,
  description: string,
  hours: number,
  fulfillsProjectHandleId?: string,
): Promise<AxiosResponse<ClaimResult> | InternalError> {
  // Make a claim
  const vcClaim: GiveVerifiableCredential = {
    "@context": "https://schema.org",
    "@type": "GiveAction",
    recipient: { identifier: toDid },
  };
  if (fromDid) {
    vcClaim.agent = { identifier: fromDid };
  }
  if (description) {
    vcClaim.description = description;
  }
  if (hours) {
    vcClaim.object = { amountOfThisGood: hours, unitCode: "HUR" };
  }
  if (fulfillsProjectHandleId) {
    vcClaim.fulfills = {
      "@type": "PlanAction",
      identifier: fulfillsProjectHandleId,
    };
  }
  // Make a payload for the claim
  const vcPayload = {
    vc: {
      "@context": ["https://www.w3.org/2018/credentials/v1"],
      type: ["VerifiableCredential"],
      credentialSubject: vcClaim,
    },
  };
  // Create a signature using private key of identity
  if (identity.keys[0].privateKeyHex == null) {
    return new Promise<InternalError>((resolve, reject) => {
      reject({
        error: "No private key",
        message:
          "Your identifier " +
          identity.did +
          " is not configured correctly. Use a different identifier.",
      });
    });
  }

  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
  const privateKeyHex: string = identity.keys[0].privateKeyHex!;
  const signer = await SimpleSigner(privateKeyHex);
  const alg = undefined;
  // Create a JWT for the request
  const vcJwt: string = await didJwt.createJWT(vcPayload, {
    alg: alg,
    issuer: identity.did,
    signer: signer,
  });

  // Make the xhr request payload

  const payload = JSON.stringify({ jwtEncoded: vcJwt });
  const url = apiServer + "/api/v2/claim";
  const token = await accessToken(identity);
  const headers = {
    "Content-Type": "application/json",
    Authorization: "Bearer " + token,
  };

  return axios.post(url, payload, { headers });
}

// from https://stackoverflow.com/a/175787/845494
//
export function isNumeric(str: string): boolean {
  return !isNaN(+str);
}

export function numberOrZero(str: string): number {
  return isNumeric(str) ? +str : 0;
}

export interface ErrorResponse {
  error?: {
    message?: string;
  };
}

export interface RateLimits {
  doneClaimsThisWeek: string;
  doneRegistrationsThisMonth: string;
  maxClaimsPerWeek: string;
  maxRegistrationsPerMonth: string;
  nextMonthBeginDateTime: string;
  nextWeekBeginDateTime: string;
}

/**
 * Represents data about a project
 **/
export interface ProjectData {
  /**
   * Name of the project
   **/
  name: string;
  /**
   * Description of the project
   **/
  description: string;
  /**
   * URL referencing information about the project
   **/
  handleId: string;
  /**
   * The Identier of the project
   **/
  rowid: string;
}

export interface VerifiableCredential {
  "@context": string;
  "@type": string;
  name: string;
  description: string;
  identifier?: string;
}

export interface WorldProperties {
  startTime?: string;
  endTime?: string;
}
