import chalk from 'chalk'
import fs from 'fs'
import { Argument, LeafCommand, Option } from 'furious-commander'
import { Reference } from 'mantaray-js'
import { join, parse } from 'path'
import { directoryExists, referenceToHex } from '../../utils'
import { BzzAddress, makeBzzAddress } from '../../utils/bzz-address'
import { createSpinner } from '../../utils/spinner'
import { VerbosityLevel } from '../root-command/command-log'
import { EnrichedFork, ManifestCommand } from './manifest-command'

export class Download extends ManifestCommand implements LeafCommand {
  public readonly name = 'download'
  public readonly description = 'Download manifest content to a folder'

  public address!: BzzAddress

  @Argument({ key: 'address', description: 'Manifest reference (with optional path)', required: true })
  public bzzUrl!: string

  @Argument({ key: 'destination', description: 'Destination folder' })
  public destination!: string

  @Option({ key: 'stdout', type: 'boolean', description: 'Print to stdout (single files only)' })
  public stdout!: boolean

  public async run(): Promise<void> {
    await super.init()

    // can be already set from other command
    if (!this.address) {
      this.address = await makeBzzAddress(this.bee, this.bzzUrl)
    }

    const forks = await this.collectForks(this.address)
    const isSingleFork = forks.length === 1
    for (const fork of forks) {
      await this.downloadFork(fork, this.address, isSingleFork)
    }
  }

  private async downloadFork(fork: EnrichedFork, address: BzzAddress, isSingleFork: boolean): Promise<void> {
    if ((!isSingleFork || !this.stdout) && !this.quiet) {
      if (this.curl) {
        this.console.log(chalk.dim(fork.path))
      } else {
        process.stdout.write(chalk.dim(fork.path))
      }
    }
    const parsedForkPath = parse(fork.path)
    const data = await this.bee.downloadData(referenceToHex(fork.node.getEntry as Reference))

    if (isSingleFork && this.stdout) {
      process.stdout.write(data)

      return
    }

    const destination = this.destination || address.hash
    const destinationFolder = join(destination, parsedForkPath.dir)

    if (!directoryExists(destinationFolder)) {
      await fs.promises.mkdir(destinationFolder, { recursive: true })
    }

    if (!this.quiet && !this.curl) {
      process.stdout.write(' ' + chalk.green('OK') + '\n')
    }
    await fs.promises.writeFile(join(destination, fork.fsPath), data)
  }

  private async collectForks(address: BzzAddress): Promise<EnrichedFork[]> {
    const spinner = createSpinner('Preparing download...')
    try {
      if (this.verbosity !== VerbosityLevel.Quiet && !this.curl) {
        spinner.start()
      }
      const forks = (await this.loadAllValueForks(address.hash, address.path)).filter(this.isDownloadableNode)

      return forks
    } finally {
      spinner.stop()
    }
  }

  private isDownloadableNode(fork: EnrichedFork): boolean {
    if (!fork.node) {
      return false
    }
    const metadata = fork.node.getMetadata

    if (metadata && (metadata['website-index-document'] || metadata['website-error-document'])) {
      return false
    }

    return fork.node.isValueType()
  }
}