use {
  super::*,
  crate::wallet::{ListDescriptorsResult, Wallet, batch, wallet_constructor::WalletConstructor},
  shared_args::SharedArgs,
};

pub mod addresses;
pub mod balance;
mod batch_command;
pub mod burn;
pub mod cardinals;
pub mod create;
pub mod dump;
pub mod inscribe;
pub mod inscriptions;
mod label;
pub mod mint;
pub mod offer;
pub mod outputs;
pub mod pending;
pub mod receive;
pub mod restore;
pub mod resume;
pub mod runics;
pub mod sats;
pub mod send;
mod shared_args;
pub mod sign;
pub mod split;
pub mod sweep;
pub mod transactions;

#[derive(Debug, Parser)]
pub(crate) struct WalletCommand {
  #[arg(long, default_value = "ord", help = "Use wallet named <WALLET>.")]
  pub(crate) name: String,
  #[arg(long, alias = "nosync", help = "Do not update index.")]
  pub(crate) no_sync: bool,
  #[arg(
    long,
    help = "Use ord running at <SERVER_URL>. [default: http://localhost:80]"
  )]
  pub(crate) server_url: Option<Url>,
  #[command(subcommand)]
  pub(crate) subcommand: Subcommand,
}

#[derive(Debug, Parser)]
#[allow(clippy::large_enum_variant)]
pub(crate) enum Subcommand {
  #[command(about = "Get wallet addresses")]
  Addresses,
  #[command(about = "Get wallet balance")]
  Balance,
  #[command(about = "Create inscriptions and runes")]
  Batch(batch_command::Batch),
  #[command(about = "Burn an inscription")]
  Burn(burn::Burn),
  #[command(about = "List unspent cardinal outputs in wallet")]
  Cardinals,
  #[command(about = "Create new wallet")]
  Create(create::Create),
  #[command(about = "Dump wallet descriptors")]
  Dump,
  #[command(about = "Create inscription")]
  Inscribe(inscribe::Inscribe),
  #[command(about = "List wallet inscriptions")]
  Inscriptions,
  #[command(about = "Export output labels")]
  Label,
  #[command(about = "Mint a rune")]
  Mint(mint::Mint),
  #[command(subcommand, about = "Offer commands")]
  Offer(offer::Offer),
  #[command(about = "List all unspent outputs in wallet")]
  Outputs(outputs::Outputs),
  #[command(about = "List pending etchings")]
  Pending(pending::Pending),
  #[command(about = "Generate receive address")]
  Receive(receive::Receive),
  #[command(about = "Restore wallet")]
  Restore(restore::Restore),
  #[command(about = "Resume pending etchings")]
  Resume(resume::Resume),
  #[command(about = "List unspent runic outputs in wallet")]
  Runics,
  #[command(about = "List wallet satoshis")]
  Sats(sats::Sats),
  #[command(about = "Send sat or inscription")]
  Send(send::Send),
  #[command(about = "Sign message")]
  Sign(sign::Sign),
  #[command(about = "Split outputs")]
  Split(split::Split),
  #[command(about = "Sweep assets from private key")]
  Sweep(sweep::Sweep),
  #[command(about = "See wallet transactions")]
  Transactions(transactions::Transactions),
}

impl WalletCommand {
  pub(crate) fn run(self, settings: Settings) -> SubcommandResult {
    match self.subcommand {
      Subcommand::Create(create) => return create.run(self.name, &settings),
      Subcommand::Restore(restore) => return restore.run(self.name, &settings),
      _ => {}
    };

    let wallet = WalletConstructor::construct(
      self.name.clone(),
      self.no_sync,
      settings.clone(),
      self
        .server_url
        .as_ref()
        .map(Url::as_str)
        .or(settings.server_url())
        .unwrap_or("http://127.0.0.1:80")
        .parse::<Url>()
        .context("invalid server URL")?,
    )?;

    match self.subcommand {
      Subcommand::Addresses => addresses::run(wallet),
      Subcommand::Balance => balance::run(wallet),
      Subcommand::Batch(batch) => batch.run(wallet),
      Subcommand::Burn(burn) => burn.run(wallet),
      Subcommand::Cardinals => cardinals::run(wallet),
      Subcommand::Create(_) | Subcommand::Restore(_) => unreachable!(),
      Subcommand::Dump => dump::run(wallet),
      Subcommand::Inscribe(inscribe) => inscribe.run(wallet),
      Subcommand::Inscriptions => inscriptions::run(wallet),
      Subcommand::Label => label::run(wallet),
      Subcommand::Mint(mint) => mint.run(wallet),
      Subcommand::Offer(offer) => offer.run(wallet),
      Subcommand::Outputs(outputs) => outputs.run(wallet),
      Subcommand::Pending(pending) => pending.run(wallet),
      Subcommand::Receive(receive) => receive.run(wallet),
      Subcommand::Resume(resume) => resume.run(wallet),
      Subcommand::Runics => runics::run(wallet),
      Subcommand::Sats(sats) => sats.run(wallet),
      Subcommand::Send(send) => send.run(wallet),
      Subcommand::Sign(sign) => sign.run(wallet),
      Subcommand::Split(split) => split.run(wallet),
      Subcommand::Sweep(sweep) => sweep.run(wallet),
      Subcommand::Transactions(transactions) => transactions.run(wallet),
    }
  }

  fn parse_metadata(cbor: Option<PathBuf>, json: Option<PathBuf>) -> Result<Option<Vec<u8>>> {
    match (cbor, json) {
      (None, None) => Ok(None),
      (Some(path), None) => {
        let cbor = fs::read(path)?;
        let _value: Value = ciborium::from_reader(Cursor::new(cbor.clone()))
          .context("failed to parse CBOR metadata")?;

        Ok(Some(cbor))
      }
      (None, Some(path)) => {
        let value: serde_json::Value =
          serde_json::from_reader(File::open(path)?).context("failed to parse JSON metadata")?;
        let mut cbor = Vec::new();
        ciborium::into_writer(&value, &mut cbor)?;

        Ok(Some(cbor))
      }
      (Some(_), Some(_)) => panic!(),
    }
  }
}
