import * as fs from 'fs';
import * as crypto from 'crypto';
import * as path from 'path';
import yargs from 'yargs';
import { hideBin } from 'yargs/helpers';
import { parse as csvParse } from 'csv-parse';
import * as csvWriter from 'csv-writer';
import dotenv from 'dotenv';

import { StargateClient } from '@cosmjs/stargate';
import { Registry } from '@cerc-io/registry-sdk';
import { decodeTxRaw, decodePubkey } from '@cosmjs/proto-signing';

dotenv.config();

const LACONICD_GQL_ENDPOINT = process.env.LACONICD_GQL_ENDPOINT || 'https://laconicd.laconic.com/api';
const LACONICD_RPC_ENDPOINT = process.env.LACONICD_RPC_ENDPOINT || 'https://laconicd.laconic.com';
const LACONICD_CHAIN_ID = process.env.LACONICD_CHAIN_ID || 'laconic_9000-1';

async function main(): Promise<void> {
  const argv = _getArgv();

  const registry = new Registry(LACONICD_GQL_ENDPOINT, LACONICD_RPC_ENDPOINT, LACONICD_CHAIN_ID);
  const client = await StargateClient.connect(LACONICD_RPC_ENDPOINT);

  console.time('time_taken_getParticipants');
  const participants = await registry.getParticipants();
  console.timeEnd('time_taken_getParticipants');

  const subscribers = await readSubscribers(argv.subscribersCsv);
  console.log('Read subscribers, count:', subscribers.length);

  await processSubscribers(client, participants, subscribers, argv.output);
}

async function readSubscribers(subscribersCsvPath: string): Promise<any> {
  const fileContent = fs.readFileSync(path.resolve(subscribersCsvPath), { encoding: 'utf-8' });
  const headers = ['subscriber_id', 'email', 'status', 'premium?', 'created_at', 'api_subscription_id'];

  return csvParse(fileContent, { delimiter: ',', columns: headers }).toArray();
}

function hashSubscriberId(subscriberId: string): string {
  return '0x' + crypto.createHash('sha256').update(subscriberId).digest('hex');
}

async function processSubscribers(client: StargateClient, participants: any[], subscribers: any[], outputPath: string) {
  // Map kyc_id to participant data
  const kycMap: Record<string, any> = {};
  participants.forEach((participant: any) => {
    kycMap[participant.kycId] = participant;
  });

  const onboardingTxsHeightMap: Record<string, { txHeight: number, pubkey: string }> = {};
  console.time('time_taken_searchTx');
  const onboardingTxs = await client.searchTx(`message.action='/cerc.onboarding.v1.MsgOnboardParticipant'`);
  console.timeEnd('time_taken_searchTx');

  console.log('Fetched onboardingTxs, count:', onboardingTxs.length);

  console.time('time_taken_decodingTxs');
  onboardingTxs.forEach(onboardingTx => {
    const rawPubkey = decodeTxRaw(onboardingTx.tx).authInfo.signerInfos[0].publicKey;
    if (!rawPubkey) {
      console.error('pubkey not found in tx', onboardingTx.hash);
      return;
    }

    const pubkey = decodePubkey(rawPubkey).value;

    // Determine sender
    const onboardParticipantEvent = onboardingTx.events.find(event => event.type === 'onboard_participant');
    if (!onboardParticipantEvent) {
      console.error('onboard_participant event not found in tx', onboardingTx.hash);
      return;
    }

    const sender = onboardParticipantEvent.attributes.find(attr => attr.key === 'signer')?.value;
    if (!sender) {
      console.error('sender not found in onboard_participant event for tx', onboardingTx.hash)
      return;
    }

    // Update if already exists
    let latesTxHeight = onboardingTx.height;
    if (onboardingTxsHeightMap[sender]) {
      latesTxHeight = latesTxHeight > onboardingTxsHeightMap[sender].txHeight ? latesTxHeight : onboardingTxsHeightMap[sender].txHeight;
    }

    onboardingTxsHeightMap[sender] = { txHeight: latesTxHeight, pubkey };
  });
  console.timeEnd('time_taken_decodingTxs');

  const onboardedSubscribers: any[] = [];
  for (let i = 0; i < subscribers.length; i++) {
    const subscriber = subscribers[i];

    const hashedSubscriberId = hashSubscriberId(subscriber['subscriber_id']);
    const participant = kycMap[hashedSubscriberId];
    if (!participant) {
      continue;
    }

    const participantAddresss = participant['cosmosAddress'];

    // Skip participant if an onboarding tx not found
    if (!onboardingTxsHeightMap[participantAddresss]) {
      continue;
    }

    const onboardedSubscriber = {
      subscriber_id: subscriber['subscriber_id'],
      email: subscriber['email'],
      status: subscriber['status'],
      'premium?': subscriber['premium?'],
      created_at: subscriber['created_at'],
      laconic_address: participantAddresss,
      nitro_address: participant['nitroAddress'],
      role: participant['role'],
      hashed_subscriber_id: participant['kycId'],
      laconic_pubkey: onboardingTxsHeightMap[participantAddresss].pubkey,
      onboarding_height: onboardingTxsHeightMap[participantAddresss].txHeight
    };

    onboardedSubscribers.push(onboardedSubscriber);
  }

  const writer = csvWriter.createObjectCsvWriter({
    path: path.resolve(outputPath),
    header: [
      { id: 'subscriber_id', title: 'subscriber_id' },
      { id: 'email', title: 'email' },
      { id: 'status', title: 'status' },
      { id: 'premium?', title: 'premium?' },
      { id: 'created_at', title: 'created_at' },
      { id: 'laconic_address', title: 'laconic_address' },
      { id: 'nitro_address', title: 'nitro_address' },
      { id: 'role', title: 'role' },
      { id: 'hashed_subscriber_id', title: 'hashed_subscriber_id' },
      { id: 'laconic_pubkey', title: 'laconic_pubkey' },
      { id: 'onboarding_height', title: 'onboarding_height' },
    ],
    alwaysQuote: true
  });

  await writer.writeRecords(onboardedSubscribers);

  console.log(`Data has been written to ${path.resolve(outputPath)}`);
}

function _getArgv (): any {
  return yargs(hideBin(process.argv))
  .option('subscribersCsv', {
    alias: 's',
    type: 'string',
    demandOption: true,
    describe: 'Path to the subscribers CSV file',
  })
  .option('output', {
    alias: 'o',
    type: 'string',
    demandOption: true,
    describe: 'Path to the output CSV file',
  })
  .help()
  .argv;
}

main().catch(err => {
  console.log(err);
});
