import fs, { ReadStream, WriteStream } from 'fs';
import { Logger } from '@w3f/logger';
import { DeriveAccountRegistration } from '@polkadot/api-derive/accounts/types';
import { EraIndex, Event } from '@polkadot/types/interfaces';
import { ApiPromise } from '@polkadot/api';
import { EraLastBlock } from './types';

export const isDirEmpty = (path: string): boolean =>{
  return fs.readdirSync(path).length === 0
}

export const isDirExistent = (path: string): boolean =>{
  return fs.existsSync(path)
}

export const makeDir = (path: string): void =>{
  fs.mkdirSync(path)
}

export const getFileNames = (sourceDir: string, logger: Logger): string[] =>{

  let names = []
  try {
    names = fs.readdirSync(sourceDir)
  } catch (error) {
    logger.error(error)
  } 
  return names
}

export const deleteFile = (filePath: string, logger: Logger): void =>{
    
  try {
    fs.unlinkSync(filePath)
    logger.info('deleted ' + filePath)
  } catch(err) {
    logger.error(err)
  }
}

export const initFile = (exportDir: string,fileName: string,logger: Logger): WriteStream => {

  const filePath = `${exportDir}/${fileName}`;
  const file = fs.createWriteStream(filePath);
  file.on('error', (err) => { logger.error(err.stack) });

  return file
}

export const closeFile = (file: WriteStream|ReadStream): Promise<void>=> {
  return new Promise(resolve => {
    file.on("close", resolve);
    file.close();
  });
}

export const getDisplayName = (identity: DeriveAccountRegistration): string =>{
  /* TODO
  This code is coming from https://github.com/mariopino/substrate-data-csv/blob/master/utils.js
  and needs to be refactored
  */

  if (
    identity.displayParent &&
    identity.displayParent !== `` &&
    identity.display &&
    identity.display !== ``
  ) {
    return `${identity.displayParent.replace(/\n/g, '')} / ${identity.display.replace(/\n/g, '')}`;
  } else {
    return identity.display || ``;
  }
}

const firstBlockCurrentEra = async (api: ApiPromise): Promise<number> => {

  const last = await api.rpc.chain.getHeader()
  const deriveSessionProgress = await api.derive.session.progress();  
  //there is an intrinsic api error that has to be corrected next => guessed
  const guessedFirstBlockCurrentEra = last.number.unwrap().toNumber() - deriveSessionProgress.eraProgress.toNumber() + 50 

  const hash = await api.rpc.chain.getBlockHash(guessedFirstBlockCurrentEra)
  const apiAt = await api.at(hash)
  const [_,firstBlockCurrentEra] = await apiAt.query.babe.epochStart()

  return firstBlockCurrentEra.toNumber()
}

const howManyErasAgo = async (eraIndex: EraIndex, api: ApiPromise): Promise<number> => {

  const currentEraIndex = (await api.query.staking.activeEra()).unwrap().index;
  return currentEraIndex.toNumber() - eraIndex.toNumber()
  
}

const lastBlockOf = async (eraIndex: EraIndex, api: ApiPromise): Promise<number> => {

  const howManyErasAgoVar = await howManyErasAgo(eraIndex, api)
  if (howManyErasAgoVar == 0) return (await api.rpc.chain.getHeader()).number.unwrap().toNumber()

  const lastBlockPreviousEra = await firstBlockCurrentEra(api) - 1  

  const deriveSessionProgress = await api.derive.session.progress();  

  // the api result is still not reliable => guessed
  const guessedResult = lastBlockPreviousEra - ( ( howManyErasAgoVar - 1 ) * deriveSessionProgress.eraLength.toNumber() )

  const hash = await api.rpc.chain.getBlockHash(guessedResult + 50)
  const apiAt = await api.at(hash)
  const [_,firstBlockNextTargetEra] = await apiAt.query.babe.epochStart()
  
  return firstBlockNextTargetEra.toNumber() - 1
  
}

export const erasLastBlock = async (indexes: EraIndex[], api: ApiPromise): Promise<EraLastBlock[]> => {

  const result = await Promise.all(indexes.map(async index => {
    return {era: index, block: await lastBlockOf(index,api)}
   }))

  return result

}

export const getErrorMessage = (error: unknown): string => {
  let errorString: string
  if (typeof error === "string") {
    errorString = error
  } else if (error instanceof Error) {
    errorString = error.message 
  }
  return errorString
}

export const delay = (ms: number): Promise<void> =>{
  return new Promise( resolve => setTimeout(resolve, ms) );
}

export const initWriteFileStream = (dirPath: string,fileName: string,logger: Logger): WriteStream => {

  const filePath = `${dirPath}/${fileName}`;
  const file = fs.createWriteStream(filePath);
  file.on('error', function(err) { logger.error(err.stack) });

  return file
}

export const initReadFileStream = (dirPath: string,fileName: string,logger: Logger): ReadStream => {

  const filePath = `${dirPath}/${fileName}`;
  const file = fs.createReadStream(filePath);
  file.on('error', function(err) { logger.error(err.stack) });

  return file
}

export const isNewEraEvent = (event: Event, api: ApiPromise): boolean => {
  return api.events.session.NewSession.is(event)
}