/**
 * Automatic payment setup flow
 *
 * This module provides an automated, non-interactive setup experience for
 * configuring payment approvals. It uses default values and command-line
 * options to complete the setup without user interaction.
 */

import { ethers } from 'ethers'
import pc from 'picocolors'
import { TELEMETRY_CLI_APP_NAME } from '../common/constants.js'
import {
  calculateDepositCapacity,
  checkAndSetAllowances,
  checkFILBalance,
  checkUSDFCBalance,
  depositUSDFC,
  getPaymentStatus,
  validatePaymentRequirements,
} from '../core/payments/index.js'
import { cleanupSynapseService, initializeSynapse } from '../core/synapse/index.js'
import { formatUSDFC } from '../core/utils/format.js'
import { getCLILogger, parseCLIAuth } from '../utils/cli-auth.js'
import { cancel, createSpinner, intro, outro } from '../utils/cli-helpers.js'
import { log } from '../utils/cli-logger.js'
import { displayAccountInfo, displayDepositWarning } from './setup.js'
import type { PaymentSetupOptions } from './types.js'

/**
 * Run automatic payment setup with defaults
 *
 * @param options - Options from command line
 */
export async function runAutoSetup(options: PaymentSetupOptions): Promise<void> {
  intro(pc.bold('Filecoin Onchain Cloud Payment Setup'))
  log.message(pc.gray('Running in auto mode...'))

  // Parse and validate deposit amount
  let targetFilecoinPayBalance: bigint
  try {
    targetFilecoinPayBalance = ethers.parseUnits(options.deposit, 18)
  } catch {
    console.error(pc.red(`Error: Invalid deposit amount '${options.deposit}'`))
    process.exit(1)
  }

  const spinner = createSpinner()
  spinner.start('Initializing connection...')

  try {
    // Parse and validate authentication
    const authConfig = parseCLIAuth(options)

    const logger = getCLILogger()
    const synapse = await initializeSynapse(
      { ...authConfig, telemetry: { sentrySetTags: { appName: TELEMETRY_CLI_APP_NAME } } },
      logger
    )
    const network = synapse.getNetwork()
    const client = synapse.getClient()
    const address = await client.getAddress()

    spinner.stop(`${pc.green('✓')} Connected to ${pc.bold(network)}`)

    // Check balances
    spinner.start('Checking balances...')

    const filStatus = await checkFILBalance(synapse)
    const walletUsdfcBalance = await checkUSDFCBalance(synapse)

    spinner.stop(`${pc.green('✓')} Balance check complete`)

    // Validate payment requirements
    const validation = validatePaymentRequirements(filStatus.hasSufficientGas, walletUsdfcBalance, filStatus.isCalibnet)
    if (!validation.isValid) {
      log.line(`${pc.red('✗')} ${validation.errorMessage}`)
      if (validation.helpMessage) {
        log.line('')
        log.line(`  ${pc.cyan(validation.helpMessage)}`)
      }
      log.flush()
      cancel('Please fund your wallet and try again')
      process.exit(1)
    }

    // Now safe to get payment status since we know account exists
    const status = await getPaymentStatus(synapse)

    // Display account and balance info using shared function
    displayAccountInfo(
      address,
      network,
      filStatus.balance,
      filStatus.isCalibnet,
      filStatus.hasSufficientGas,
      walletUsdfcBalance,
      status.filecoinPayBalance
    )

    // Get storage pricing for capacity calculation
    const storageInfo = await synapse.storage.getStorageInfo()
    const pricePerTiBPerEpoch = storageInfo.pricing.noCDN.perTiBPerEpoch

    // Track if any changes were made
    let actionsTaken = false
    let actualFilecoinPayTopUp = 0n

    if (status.filecoinPayBalance < targetFilecoinPayBalance) {
      const neededFilecoinPayTopUp = targetFilecoinPayBalance - status.filecoinPayBalance
      actualFilecoinPayTopUp = neededFilecoinPayTopUp

      if (neededFilecoinPayTopUp > walletUsdfcBalance) {
        console.error(
          pc.red(
            `✗ Insufficient USDFC for deposit (need ${formatUSDFC(neededFilecoinPayTopUp)} USDFC, have ${formatUSDFC(walletUsdfcBalance)} USDFC)`
          )
        )
        process.exit(1)
      }

      spinner.start(`Depositing ${formatUSDFC(neededFilecoinPayTopUp)} USDFC...`)
      const { depositTx } = await depositUSDFC(synapse, neededFilecoinPayTopUp)
      spinner.stop(`${pc.green('✓')} Deposited ${formatUSDFC(neededFilecoinPayTopUp)} USDFC`)
      actionsTaken = true

      log.line(pc.bold('Transaction details:'))
      log.indent(pc.gray(`Deposit: ${depositTx}`))
      log.flush()
    } else {
      // Use a dummy spinner to get consistent formatting
      spinner.start('Checking deposit...')
      const { updated, transactionHash } = await checkAndSetAllowances(synapse)
      if (updated) {
        spinner.stop(`${pc.green('✓')} Updated payment allowances, tx: ${transactionHash}`)
      } else {
        spinner.stop(`${pc.green('✓')} Deposit already sufficient (${formatUSDFC(status.filecoinPayBalance)} USDFC)`)
      }
    }

    // Calculate capacity for final summary
    const totalDeposit = status.filecoinPayBalance + actualFilecoinPayTopUp
    const capacity = calculateDepositCapacity(totalDeposit, pricePerTiBPerEpoch)

    // Final summary
    spinner.start('Completing setup...')
    spinner.stop('━━━ Configuration Summary ━━━')

    log.line(`Network: ${pc.bold(network)}`)
    log.line(`Deposit: ${formatUSDFC(totalDeposit)} USDFC`)

    if (capacity.gibPerMonth > 0) {
      const capacityStr =
        capacity.gibPerMonth >= 1024
          ? `${(capacity.gibPerMonth / 1024).toFixed(1)} TiB`
          : `${capacity.gibPerMonth.toFixed(1)} GiB`
      log.line(`Storage: ~${capacityStr} for 1 month`)
    }

    log.line(`Status: ${pc.green('Ready to upload')}`)
    log.flush()

    // Show deposit warning if needed
    displayDepositWarning(totalDeposit, status.currentAllowances.lockupUsed)

    // Show appropriate outro message based on whether actions were taken
    if (actionsTaken) {
      outro('Payment setup completed successfully')
    } else {
      outro('Payment setup already configured - ready to use')
    }
  } catch (error) {
    spinner.stop() // Stop spinner without message
    console.error(pc.red('✗ Setup failed'))
    console.error(pc.red('Error:'), error instanceof Error ? error.message : error)

    process.exitCode = 1
  } finally {
    await cleanupSynapseService()
    process.exit()
  }
}
