import { Types } from '../../../bindings/mina-transaction/v1/types.js';
import { UInt32 } from '../../provable/int.js';
import { TokenId, type ZkappCommand } from './account-update.js';
import type { ActionStatesStringified } from './fetch.js';

export {
  accountQuery,
  currentSlotQuery,
  genesisConstantsQuery,
  getActionsQuery,
  getEventsQuery,
  lastBlockQuery,
  lastBlockQueryFailureCheck,
  removeJsonQuotes,
  sendZkappQuery,
  transactionStatusQuery,
  type ActionQueryResponse,
  type ActionsQueryInputs,
  type CurrentSlotResponse,
  type DepthOptions,
  type EpochData,
  type EventActionFilterOptions,
  type EventQueryResponse,
  type EventsQueryInputs,
  type FailureReasonResponse,
  type FetchedAccount,
  type FetchedAccountResponse,
  type FetchedAction,
  type FetchedBlock,
  type GenesisConstantsResponse,
  type LastBlockQueryFailureCheckResponse,
  type LastBlockQueryResponse,
  type SendZkAppResponse,
  type TransactionDepthInfo,
  type TransactionStatus,
  type TransactionStatusQueryResponse,
};

// removes the quotes on JSON keys
function removeJsonQuotes(json: string) {
  let cleaned = JSON.stringify(JSON.parse(json), null, 2);
  return cleaned.replace(/\"(\S+)\"\s*:/gm, '$1:');
}

type ActionsQueryInputs = {
  /** Public key of the ZkApp to which actions have been emitted */
  publicKey: string;
  actionStates?: ActionStatesStringified;
  tokenId?: string;
  /** Block number to query from */
  from?: number;
  /** Block number to query to */
  to?: number;
};

type EventsQueryInputs = {
  /** Public key of the ZkApp to which events have been emitted */
  publicKey: string;
  tokenId?: string;
  /** Block number to query from */
  from?: number;
  /** Block number to query to */
  to?: number;
};

type AuthRequired = Types.Json.AuthRequired;
// TODO auto-generate this type and the query
type FetchedAccount = {
  publicKey: string;
  token: string;
  nonce: string;
  balance: { total: string };
  tokenSymbol: string | null;
  receiptChainHash: string | null;
  timing: {
    initialMinimumBalance: string | null;
    cliffTime: string | null;
    cliffAmount: string | null;
    vestingPeriod: string | null;
    vestingIncrement: string | null;
  };
  permissions: {
    editState: AuthRequired;
    access: AuthRequired;
    send: AuthRequired;
    receive: AuthRequired;
    setDelegate: AuthRequired;
    setPermissions: AuthRequired;
    setVerificationKey: {
      auth: AuthRequired;
      txnVersion: string;
    };
    setZkappUri: AuthRequired;
    editActionState: AuthRequired;
    setTokenSymbol: AuthRequired;
    incrementNonce: AuthRequired;
    setVotingFor: AuthRequired;
    setTiming: AuthRequired;
  } | null;
  delegateAccount: { publicKey: string } | null;
  votingFor: string | null;
  zkappState: string[] | null;
  verificationKey: { verificationKey: string; hash: string } | null;
  actionState: string[] | null;
  provedState: boolean | null;
  zkappUri: string | null;
};
type FetchedAccountResponse = {
  account: FetchedAccount;
};

type EpochData = {
  ledger: {
    hash: string;
    totalCurrency: string;
  };
  seed: string;
  startCheckpoint: string;
  lockCheckpoint: string;
  epochLength: string;
};

type LastBlockQueryResponse = {
  bestChain: {
    protocolState: {
      blockchainState: {
        snarkedLedgerHash: string;
        stagedLedgerHash: string;
        date: string;
        utcDate: string;
        stagedLedgerProofEmitted: boolean;
      };
      previousStateHash: string;
      consensusState: {
        blockHeight: string;
        slotSinceGenesis: string;
        slot: string;
        nextEpochData: EpochData;
        stakingEpochData: EpochData;
        epochCount: string;
        minWindowDensity: string;
        totalCurrency: string;
        epoch: string;
      };
    };
  }[];
};

type FailureReasonResponse = {
  failures: string[];
  index: number;
}[];

type LastBlockQueryFailureCheckResponse = {
  bestChain: {
    transactions: {
      zkappCommands: {
        hash: string;
        failureReason: FailureReasonResponse;
      }[];
    };
    stateHash: string;
    protocolState: {
      consensusState: {
        blockHeight: string;
        epoch: string;
        slotSinceGenesis: string;
      };
      previousStateHash: string;
    };
  }[];
};

type FetchedBlock = {
  protocolState: {
    blockchainState: {
      snarkedLedgerHash: string; // hash-like encoding
      stagedLedgerHash: string; // hash-like encoding
      date: string; // String(Date.now())
      utcDate: string; // String(Date.now())
      stagedLedgerProofEmitted: boolean; // bool
    };
    previousStateHash: string; // hash-like encoding
    consensusState: {
      blockHeight: string; // String(number)
      slotSinceGenesis: string; // String(number)
      slot: string; // String(number)
      nextEpochData: {
        ledger: {
          hash: string; // hash-like encoding
          totalCurrency: string; // String(number)
        };
        seed: string; // hash-like encoding
        startCheckpoint: string; // hash-like encoding
        lockCheckpoint: string; // hash-like encoding
        epochLength: string; // String(number)
      };
      stakingEpochData: {
        ledger: {
          hash: string; // hash-like encoding
          totalCurrency: string; // String(number)
        };
        seed: string; // hash-like encoding
        startCheckpoint: string; // hash-like encoding
        lockCheckpoint: string; // hash-like encoding
        epochLength: string; // String(number)
      };
      epochCount: string; // String(number)
      minWindowDensity: string; // String(number)
      totalCurrency: string; // String(number)
      epoch: string; // String(number)
    };
  };
};

type GenesisConstantsResponse = {
  genesisConstants: {
    genesisTimestamp: string;
    coinbase: string;
    accountCreationFee: string;
  };
  daemonStatus: {
    consensusConfiguration: {
      epochDuration: string;
      k: string;
      slotDuration: string;
      slotsPerEpoch: string;
    };
  };
};

type CurrentSlotResponse = {
  bestChain: Array<{
    protocolState: {
      consensusState: {
        slot: number;
        slotSinceGenesis: number;
      };
    };
  }>;
};

/**
 * INCLUDED: A transaction that is on the longest chain
 *
 * PENDING: A transaction either in the transition frontier or in transaction pool but is not on the longest chain
 *
 * UNKNOWN: The transaction has either been snarked, reached finality through consensus or has been dropped
 *
 */
type TransactionStatus = 'INCLUDED' | 'PENDING' | 'UNKNOWN';

type TransactionStatusQueryResponse = {
  transactionStatus: TransactionStatus;
};

/**
 * Information about a transaction's depth (confirmation count) in the blockchain.
 * Depth represents how many blocks have been built on top of the block containing the transaction.
 *
 * @see https://docs.minaprotocol.com/mina-protocol/lifecycle-of-a-payment
 */
type TransactionDepthInfo = {
  /** Number of blocks built on top of the block containing the transaction (0 = just included) */
  depth: number;
  /** Block height at which the transaction was included */
  inclusionBlockHeight: number;
  /** Current block height of the chain */
  currentBlockHeight: number;
  /** Whether the transaction has reached finality (depth >= finalityThreshold) */
  isFinalized: boolean;
  /** The finality threshold used for this calculation */
  finalityThreshold: number;
};

/**
 * Options for querying transaction depth.
 */
type DepthOptions = {
  /** Number of blocks to search for the transaction (default: 20) */
  blockLength?: number;
  /**
   * Number of blocks required for finality (default: 15).
   * Default of 15 blocks provides 99.9% confidence per Mina documentation.
   * @see https://docs.minaprotocol.com/mina-protocol/lifecycle-of-a-payment
   */
  finalityThreshold?: number;
};

type SendZkAppResponse = {
  sendZkapp: {
    zkapp: {
      hash: string;
      id: string;
      zkappCommand: ZkappCommand;
      failureReasons: FailureReasonResponse;
    };
  };
};

type EventQueryResponse = {
  events: {
    blockInfo: {
      distanceFromMaxBlockHeight: number;
      globalSlotSinceGenesis: number;
      height: number;
      stateHash: string;
      parentHash: string;
      chainStatus: string;
    };
    eventData: {
      transactionInfo: {
        hash: string;
        memo: string;
        status: string;
      };
      data: string[];
    }[];
  }[];
};

type FetchedAction = {
  blockInfo: {
    distanceFromMaxBlockHeight: number;
  };
  actionState: {
    actionStateOne: string;
    actionStateTwo: string;
  };
  actionData: {
    accountUpdateId: string;
    data: string[];
    transactionInfo?: {
      sequenceNumber: number;
      zkappAccountUpdateIds: number[];
    };
  }[];
};

type ActionQueryResponse = {
  actions: FetchedAction[];
};

type EventActionFilterOptions = {
  to?: UInt32;
  from?: UInt32;
};

const transactionStatusQuery = (txId: string) => `query {
    transactionStatus(zkappTransaction:"${txId}")
  }`;

const getEventsQuery = (inputs: EventsQueryInputs) => {
  inputs.tokenId ??= TokenId.toBase58(TokenId.default);
  const { publicKey, tokenId, to, from } = inputs;
  let input = `address: "${publicKey}", tokenId: "${tokenId}"`;
  if (to !== undefined) {
    input += `, to: ${to.toString()}`;
  }
  if (from !== undefined) {
    input += `, from: ${from.toString()}`;
  }
  return `{
  events(input: { ${input} }) {
    blockInfo {
      distanceFromMaxBlockHeight
      height
      globalSlotSinceGenesis
      stateHash
      parentHash
      chainStatus
    }
    eventData {
      transactionInfo {
        hash
        memo
        status
      }
      data
    }
  }
}`;
};

const getActionsQuery = (inputs: ActionsQueryInputs) => {
  inputs.tokenId ??= TokenId.toBase58(TokenId.default);
  const { publicKey, tokenId, actionStates, from, to } = inputs;
  const { fromActionState, endActionState } = actionStates ?? {};
  let input = `address: "${publicKey}", tokenId: "${tokenId}"`;
  if (fromActionState !== undefined) {
    input += `, fromActionState: "${fromActionState}"`;
  }
  if (endActionState !== undefined) {
    input += `, endActionState: "${endActionState}"`;
  }
  if (to !== undefined) {
    input += `, to: ${to}`;
  }
  if (from !== undefined) {
    input += `, from: ${from}`;
  }
  return `{
    actions(input: { ${input} }) {
      blockInfo {
        distanceFromMaxBlockHeight
      }
      actionState {
        actionStateOne
        actionStateTwo
      }
      actionData {
        accountUpdateId
        data
        transactionInfo { 
          sequenceNumber 
          zkappAccountUpdateIds 
        }
      }
    }
  }`;
};

const genesisConstantsQuery = `{
    genesisConstants {
      genesisTimestamp
      coinbase
      accountCreationFee
    }
    daemonStatus {
      consensusConfiguration {
        epochDuration
        k
        slotDuration
        slotsPerEpoch
      }
    }
  }`;

const lastBlockQuery = `{
  bestChain(maxLength: 1) {
    protocolState {
      blockchainState {
        snarkedLedgerHash
        stagedLedgerHash
        date
        utcDate
        stagedLedgerProofEmitted
      }
      previousStateHash
      consensusState {
        blockHeight
        slotSinceGenesis
        slot
        nextEpochData {
          ledger {hash totalCurrency}
          seed
          startCheckpoint
          lockCheckpoint
          epochLength
        }
        stakingEpochData {
          ledger {hash totalCurrency}
          seed
          startCheckpoint
          lockCheckpoint
          epochLength
        }
        epochCount
        minWindowDensity
        totalCurrency
        epoch
      }
    }
  }
}`;

const lastBlockQueryFailureCheck = (length: number) => `{
  bestChain(maxLength: ${length}) {
    transactions {
      zkappCommands {
        hash
        failureReason {
          failures
          index
        }
      }
    }
    stateHash
    protocolState {
      consensusState {
        blockHeight
        epoch
        slotSinceGenesis
      }
      previousStateHash
    }
  }
}`;

// TODO: Decide an appropriate response structure.
function sendZkappQuery(json: string) {
  return `mutation {
  sendZkapp(input: {
    zkappCommand: ${removeJsonQuotes(json)}
  }) {
    zkapp {
      hash
      id
      failureReason {
        failures
        index
      }
      zkappCommand {
        memo
        feePayer {
          body {
            publicKey
          }
        }
        accountUpdates {
          body {
            publicKey
            useFullCommitment
            incrementNonce
          }
        }
      }
    }
  }
}
`;
}

const accountQuery = (publicKey: string, tokenId: string) => `{
  account(publicKey: "${publicKey}", token: "${tokenId}") {
    publicKey
    token
    nonce
    balance { total }
    tokenSymbol
    receiptChainHash
    timing {
      initialMinimumBalance
      cliffTime
      cliffAmount
      vestingPeriod
      vestingIncrement
    }
    permissions {
      editState
      access
      send
      receive
      setDelegate
      setPermissions
      setVerificationKey {
        auth
        txnVersion
      }
      setZkappUri
      editActionState
      setTokenSymbol
      incrementNonce
      setVotingFor
      setTiming
    }
    delegateAccount { publicKey }
    votingFor
    zkappState
    verificationKey {
      verificationKey
      hash
    }
    actionState
    provedState
    zkappUri
  }
}
`;

const currentSlotQuery = `{
    bestChain(maxLength: 1) {
      protocolState {
        consensusState {
          slot
          slotSinceGenesis
        }
      }
    }
}`;
