import { PrivKey } from '@ts-bitcoin/core';
import { JWK, FlattenedJWSInput, KeyLike, GeneralDecryptGetKey, GeneralVerifyGetKey } from 'jose';
import { Key } from 'univrse';

export type API_VERSIONS = '1' | '2' | string;

/**
 * Options to pass to Mintblue.create()
 *
 * @category SDK
 */
export interface MintblueOptions {
  token: string; // an SDK token

  SDKTokenPrefix?: string;
  ServerTokenPrefix?: string;

  url?: string;
  version?: API_VERSIONS;
  userKeyAttributes?: UserKeyAttributes;
  keys?: Keys;
}

export type MintblueConfig = {
  apiToken: string; // an API token
  url: string;
  version: API_VERSIONS;
  keys: Keys;
};
export interface UserKeyAttributes {
  masterKeyEncryptedWithKek: string;
  masterKeyEncryptedWithRecoveryKey: string;
  privateKeyEncryptedWithMasterKey: string;
  recoveryKeyEncryptedWithMasterKey: string;
  publicKeyHex: string;
  kekSalt: string;
  kekIterations: number;
}

export interface CreateTransactionOptions {
  /** ID of the project to publish to */
  project_id: string;
  /** metadata is indexed locally, but not published to the blockchain. Can be any key/value  */
  metadata?: { [key: string]: any };
  /** Describes the actual content to be published to the blockchain. A transaction can contain one or multiple outputs of various types.   */
  outputs: Output[];
  /** If the response should not return the rawtx, set to false */
  rawtx?: boolean;
}
export interface GetTransactionOptions {
  txid: string;
  parse: boolean; // When set to true, the result includes an 'outputs' field containing the parsed outputs
  secret?: string;
}

/**
 * @category Outputs
 */
export type Output =
  | DataOutput
  | PaymentOutput
  | ScriptOutput
  | FileOutput
  | EnvelopeOutput
  | PigiOutput
  | PeppolOutput
  | DocV1Output
  | HashOutput
  | TreeOutput
  | UndefinedOutput;

export enum OutputType {
  data = 'data',
  payment = 'payment',
  script = 'script',
  file = 'file',
  pigi = 'pigi',
  peppol = 'peppol',
  doc = 'doc',
  hash = 'hash',
  tree = 'tree',
  undefined = 'undefined',
}
/** Output type for general data packets. Can optionally include encryption and/or digital signatures.
 *
 * @category Outputs
 */
export interface DataOutput {
  type: 'data';
  /** Data to publish */
  value: string | string[] | Buffer;
  /** Set to true to add digital signature */
  sign?: boolean;
  /** Set to true to encrypt value */
  encrypt?: boolean;
}

/**
 * Output type for micropayments.
 * @category Outputs
 */
export interface PaymentOutput {
  type: 'payment';
  /** Recipient's Paymail address or BSV address */
  to: string;
  /** Payment value. */
  satoshis: number;
}

/**
 * Output type for nature restoration.
 * @category Outputs
 * */
export interface TreeOutput {
  type: 'tree';
  /** Trees to plant (23500000sats/tree) */
  trees: number;
}

/**
 * Output type for advanced Bitcoin scripts.
 * @category Outputs
 * */
export interface ScriptOutput {
  type: 'script';
  /** Hex encoded Bitcoin script */
  value: string;
  /** encoding, defaults to hex  */
  encoding?: 'asm' | 'hex' | 'base64';
}

/**
 * Output type for files.
 * @category Outputs
 * */
export interface FileOutput {
  type: 'file';
  /** File to publish */
  value: {
    contentType: string;
    content: Buffer;
  };
  /** Set to true to add digital signature */
  sign?: boolean;
  /** Set to true to encrypt value */
  encrypt?: boolean;
}

/** @ignore */
export interface EnvelopeOutput {
  type: 'env';
  value: any; // TODO: improve type
}

/**
 * Output type for Peppol blockchain protocol.
 * @category Outputs
 * */
export interface PeppolOutput {
  type: 'peppol';
  /** Protocol version */
  version: 1;
  /** Content (AS4 payload) to publish encrypted to blockchain. */
  value: Buffer;
  /** Uncompressed public key in hex format or JWK format */
  pubKey: string | JWK;
  /** Optional key to add digital signature in JWK format */
  signingKey?: JWK;
  /** Optional extra data to publish in blockchain transaction */
  options?: {
    extra: Buffer;
  };
}

/**
 * Output type for Pigi protocol.
 * @category Outputs
 * */
export interface PigiOutput {
  type: 'pigi';
  /** Protocol version */
  version: 1 | 2;
  options: {
    filename: string;
    mimetype: string;
  };
  /** Content (UBL payload) to publish encrypted to blockchain. */
  value: Uint8Array;
  /** Optional key to add digital signature in JWK format */
  signingKey?: JWK;
  /** @ignore */
  info?: any;
}

/** @ignore */
export interface UndefinedOutput {
  type: 'undefined';
  value?: string;
}

export interface CreateProjectOptions {
  /** Name of the project */
  name: string;
  /** Optional description */
  description?: string;
  /** Optional array of tags (can be any string) */
  tags?: string[];
  /** @ignore */
  default_key_id?: string;
}
export interface PaginationOptions {
  limit: number;
  offset: number;
}

export interface experimentalStreamProjectOptions {
  project_id: string;
  history: boolean; // If true, includes transactions that are already in the project
  last_event_id?: string; // Only show events after this event (combine this with history = true)
  onTransaction: (tx: experimentalStreamedTransaction) => unknown;
  onError: (err: Error) => unknown;
}

export interface experimentalStreamedTransaction {
  txid: string;
  status: number;
  block: null | number;
  metadata: Record<string, any>;
  privdata: Record<string, any>;
  project_id: string;
  user_id: string;
  size: number;
  created_at: string;
  published_at: string;
  confirmed_at: null | string;
  rawtx: string;
  count: string;
}

export interface ListTransactionsOptions {
  project_id?: string;
  startDate?: Date;
  endDate?: Date;
  sort?:
    | 'txid'
    | 'status'
    | 'block'
    | 'metadata'
    | 'privdata'
    | 'project_id'
    | 'user_id'
    | 'size'
    | 'created_at'
    | 'published_at'
    | 'confirmed_at';
  order?: 'asc' | 'desc';
  paginationOptions?: PaginationOptions;
}

export type ISOString = string;

export type ListTransactionsResult = Transaction[];
export interface Transaction {
  txid: string;
  status: TransactionStatus;
  block: number;
  metadata: Record<string, any>;
  privdata: Record<string, any>;
  project_id: string;
  user_id: string;
  size: number;
  created_at: ISOString;
  published_at: ISOString;
  confirmed_at: ISOString;
  count: string;
}

export type TransactionWithRawTX = Transaction & {
  rawtx: string;
};

export type TransactionWithParsedOutputs = TransactionWithRawTX & {
  outputs: Output[];
};
export interface Project {
  id: string;
  name: string;
  tags: string[];
  total_transactions_count: number;
  total_transactions_size: number;
  avg_transactions_size: number;
  date_created: string;
}
export interface CreatedTransaction {
  txid: string;
  rawtx: string;
}
export interface Keys {
  masterKey: CryptoKey;
  recoveryKey: CryptoKey;
  privateKey: PrivKey;
  publicKeyHex: string;
  kek: CryptoKey;
  additionalKeys: AdditionalKeys;
}

export type AdditionalKey = {
  info: string;
  kid: string;
  jwk: JWK;
  cryptoKey: CryptoKey;
};

export type AdditionalKeys = Array<AdditionalKey>;

export interface CreateAccesstokenOptions {
  name: string;
  expires_at?: Date;
  scopes?: Scope[];
}

export type Scope =
  | 'account.read'
  | 'account.update'
  | 'account.delete'
  | 'billing.read'
  | 'billing.update'
  | 'invite.create'
  | 'invite.read'
  | 'invite.delete'
  | 'accesstoken.create'
  | 'accesstoken.delete'
  | 'key.create'
  | 'key.read'
  | 'key.update'
  | 'key.delete'
  | 'user.read'
  | 'user.update'
  | 'user.delete'
  | 'event.create'
  | 'transaction.create';

export enum TransactionStatus {
  pending = 0,
  queued = 1,
  published = 2,
  failed = 3,
  confirmed = 4,
}

export const MB_SIGNING_KEY = 'mintblue_signing';

export const MB_DERIVING_KEY = 'mintblue_deriving';

export interface PigiAPIResponse {
  pigi: {
    version: number;
    algorithm: string;
    encryptedSize: number;
    _algorithmIndex: number;
    _idHex: string;
  };
  bitcoin?: {
    fee: number;
    size: number;
    txid: string;
    explore: {
      whatsonchain: string;
      blockchair: string;
    };
  };
  identifiers: {
    file: string[];
    raw: {
      fileid: string;
      secret: string;
      txid: string;
    };
    transaction: string[];
  };
  info: {
    file: string[];
    transaction: string[];
  };
}

export interface ApiResponse<T> {
  data: T;
  message: string;
  error: any;
}

export interface EventListener {
  id: string;
  name: string;
  trigger: {
    type: keyof TriggerTypes;
    options: TriggerTypes[keyof TriggerTypes];
  };
  actions: {
    type: keyof ActionTypes;
    options: ActionTypes[keyof ActionTypes];
  }[];
}

export type CreateEventListenerOptions = Omit<EventListener, 'id'>;

export type ActionTypes = {
  webhook: ActionWebhookOptions;
  'insert-into-project': ActionInsertIntoProjectOptions;
};

export interface ActionWebhookOptions {
  url: string;
}

export interface ActionInsertIntoProjectOptions {
  project_id: string;
}

export interface TriggerBitcomOptions {
  bitcom_protocol_id: string;
}

export interface TriggerNewTwetchFollowerOptions {
  twetchLeader: string;
}

export interface TriggerPaymentFromOptions {
  from_address: string;
}

export interface TriggerPaymentToOptions {
  to_address: string;
}

export interface TriggerMinPaymentToOptions {
  to_address: string;
  min_satoshis: number;
}

export interface TriggerBitqueryOptions {
  bitquery: string;
}

export interface TriggerPeppolOptions {
  pubKey: string;
}

export interface TriggerMintblueTransactionOptions {
  project_id: string;
  txo: any;
}

export interface TriggerMintBlueFundingOptions {
  to_address: string;
  service_name: string;
}

export type TriggerTypes = {
  BITCOM: TriggerBitcomOptions;
  NEW_TWETCH_FOLLOWER: TriggerNewTwetchFollowerOptions;
  PAYMENT_FROM: TriggerPaymentFromOptions;
  PAYMENT_TO: TriggerPaymentToOptions;
  MIN_PAYMENT_TO: TriggerMinPaymentToOptions;
  BITQUERY: TriggerBitqueryOptions;
  PEPPOL: TriggerPeppolOptions;
  'mintblue.transaction.inserted': TriggerMintblueTransactionOptions;
  'mintblue.transaction.created': TriggerMintblueTransactionOptions;
  'mintblue.funding.seen': TriggerMintBlueFundingOptions;
};

/**
 * An output definition as would be passed via createTransaction
 */
export interface DocV1Output {
  type: 'doc';
  /**
   * The raw original data
   */
  data: Uint8Array;
  /**
   * Array of JWK (private keys)
   */
  signers: JWK[];
  /**
   * Optional data to include
   */
  options: {
    /**
     * Any additional metadata
     */
    meta: any;
    /**
     * A filename
     */
    filename: string;
    /**
     * A mimetype
     */
    mimetype: string;
  };
  /**
   * Optional array of JWK (public keys) or hexadecimal public keys (compressed or uncompressed)
   * To encrypto to additional third parties
   */
  receivers?: JWK[] | string[];
  /**
   * How many PBKDF2 iterations to do (default 200000)
   */
  iterations?: number;
}

/**
 * Info that we can rebuild from both parsing and creating
 */
export interface DocV1Info {
  type: 'doc';
  hash: Uint8Array;
  signatures: Omit<FlattenedJWSInput, 'payload'>[];
  secret1: Uint8Array;
  secret1Hex: string;
  secret1Base62: string;
  secret2: Uint8Array;
  secret2Hex: string;
  secret2Base62: string;
  data: Uint8Array;
  iterations: number;
  encryptedData: Uint8Array;
  receiverKeyIds: string[];
  PROTOCOL_ID: string;
  safeScriptArray: any[];
  options: {
    meta: any;
    filename: string;
    mimetype: string;
  };
}

export interface DocV1ParseOptions {
  encryptedData: Uint8Array;
  secret1: Uint8Array;
  secret2: Uint8Array;
  receiverKey: KeyLike | GeneralDecryptGetKey | JWK;
  verifyKey?: KeyLike | GeneralVerifyGetKey | JWK;
}

type HashOutputAlgorithms = 'SHA-256' | 'SHA-512';

/**
 * Output definition to be provided when creating a HashOuput via createTransaction
 */
export interface HashOutput {
  type: 'hash';
  /** File raw contents. Not stored on-chain */
  data: Uint8Array;
  /** Hash algorithm. Either 'SHA-256' or 'SHA-512' */
  algorithm: HashOutputAlgorithms;
  /** If true the generated data will be signed */
  sign?: boolean;
  /** If true the generated data will be encrypted */
  encrypt?: boolean;
  //** Required if data is encrypted or signed */
  key?: Key;
}

/**
 * Hash data generated info. This is information that can be recovered, regardless if we are creating or reading
 */
export interface HashInfo {
  /** Output type */
  type: 'hash';
  /** File contents digest as Hex. Not stored on-chain */
  digestHex: string;
  /** File contents digest as Uint8Array. Stored on-chain */
  digest: Uint8Array;
  /** Hash algorithm used to generate digest. Either 'SHA-256' or 'SHA-512' */
  algorithm: HashOutputAlgorithms;
  /** If true the data has been signed */
  sign?: boolean;
  /** If true the data has been encrypted */
  encrypt?: boolean;
}

export interface GetProjectOptions {
  id: string; // the project id
}

export interface GetEventListenerOptions {
  id: string; // the event listener id
}

export interface UpdateProjectOptions {
  id: string;
  data: CreateProjectOptions;
}

export interface GetProjectOptions {
  id: string; // the project id
}

export type LoginOptions = MintblueOptions & {
  email: string;
  password: string;
};

export interface QueryTransactionOptions {
  where: TransactionQueryWhere;
  limit?: number;
  offset?: number;
  start_date?: Date;
  end_date?: Date;
  sort?: string;
  order?: 'asc' | 'desc' | 'ASC' | 'DESC';
  rawtx?: boolean;
}

type QueryValue = string | LikeQueryValue;
type NestedQueryValue = {
  [x: string]: QueryValue;
};
interface LikeQueryValue {
  $like: string;
}

interface AllowedColumns {
  txid: QueryValue;
  block: QueryValue;
  status: QueryValue;
  project_id: QueryValue;
  metadata: NestedQueryValue;
  privdata: NestedQueryValue;
  created_at: QueryValue;
  published_at: QueryValue;
  confirmed_at: QueryValue;
}
type TransactionQueryWhere = Partial<AllowedColumns>;
