use crate::{
    commands::{
        newton_dashboard::NewtonDashboardCommand, policy::PolicyCommand, policy_client::PolicyClientCommand,
        policy_data::PolicyDataCommand, policy_files::PolicyFilesCommand, task::TaskCommand,
    },
    config::NewtonCliConfig,
};
use clap::{Parser, Subcommand};
use newton_cli_runner::NewtonRunner;
use newton_prover_core::config::{
    log::{init_logger, LogFormat, LoggerConfig},
    NewtonAvsConfigBuilder,
};
use std::{ffi::OsString, path::PathBuf};
use tracing::info;

/// newton protocol cli entry point interface
#[derive(Debug, Parser)]
#[command(author, about = "Newton protocol cli", long_about = None)]
pub struct NewtonCli {
    /// chain id
    #[arg(long, value_name = "CHAIN_ID", global = true, env = "CHAIN_ID")]
    chain_id: Option<u64>,

    /// optional path to the configuration file
    #[arg(long, value_name = "FILE", global = true)]
    config_path: Option<PathBuf>,

    /// log format
    #[arg(
        long,
        value_enum,
        default_value = "minimal",
        global = true,
        help = "Log format: full, compact, pretty, json, or minimal"
    )]
    log_format: LogFormat,

    /// Suppress verbose config loading logs
    #[arg(long, global = true)]
    quiet: bool,

    #[command(subcommand)]
    command: Commands,
}

impl NewtonCli {
    /// parsers only the default cli arguments
    pub fn parse_args() -> Self {
        Self::parse()
    }

    /// parsers only the default cli arguments from the given iterator
    pub fn try_parse_args_from<I, T>(itr: I) -> Result<Self, clap::error::Error>
    where
        I: IntoIterator<Item = T>,
        T: Into<OsString> + Clone,
    {
        Self::try_parse_from(itr)
    }

    /// Resolve config path: use provided path, or check ~/.newton/newton-cli.toml, or None
    fn resolve_config_path(&self) -> Option<PathBuf> {
        if let Some(path) = &self.config_path {
            Some(path.clone())
        } else {
            let home = std::env::var("HOME").ok()?;
            let path = std::path::PathBuf::from(home).join(".newton").join("newton-cli.toml");
            if path.exists() {
                Some(path)
            } else {
                None
            }
        }
    }

    /// Try to load config, falling back to minimal config if service config loading fails
    /// This is for commands that don't actually use the config (marked with _config)
    /// With bundled deployment files, contract addresses should always load successfully.
    fn load_config_or_minimal(&self, chain_id: u64) -> newton_prover_core::config::NewtonAvsConfig<NewtonCliConfig> {
        let mut builder = NewtonAvsConfigBuilder::new(chain_id);
        if let Some(config_path) = self.resolve_config_path() {
            info!("Loading cli config from: {:?}", config_path);
            builder = builder.with_service_path(config_path);
        }

        // Try to load full config - should work now with bundled deployment files
        match builder.build::<NewtonCliConfig>() {
            Ok(config) => config,
            Err(e) => {
                // If service config loading fails, create minimal config with defaults
                // Deployment files are bundled, so we can still load contract addresses
                tracing::debug!("Service config loading failed, using defaults: {}", e);
                let env = std::env::var("DEPLOYMENT_ENV").unwrap_or_else(|_| "stagef".to_string());
                let service = NewtonCliConfig {
                    eth_rpc_url: "http://127.0.0.1:8545".to_string(),
                    newton_rpc_url: "http://127.0.0.1:8545".to_string(),
                    signer: newton_prover_core::config::key::EcdsaKey {
                        private_key: None,
                        keystore_path: None,
                        keystore_password: None,
                    },
                    dashboard_bearer_token: None,
                    dashboard_api_base_url: None,
                };
                // Load contract addresses from bundled files, then construct config manually
                use newton_prover_core::config::{contracts::*, ipfs::IpfsConfig, rpc::RpcProviderConfig};
                // Try to load contracts from bundled files (should succeed)
                // Clippy: use unwrap_or instead of unwrap_or_else when error isn't used
                #[allow(clippy::unnecessary_lazy_evaluations)]
                let contracts = ContractsConfig::load(chain_id, env.clone()).unwrap_or_else(|_| {
                    // Shouldn't happen with bundled files, but provide fallback
                    ContractsConfig {
                        avs: NewtonAvsContractsConfig {
                            newton_prover_service_manager: alloy::primitives::Address::ZERO,
                            newton_prover_task_manager: alloy::primitives::Address::ZERO,
                            challenge_verifier: alloy::primitives::Address::ZERO,
                            rego_verifier: alloy::primitives::Address::ZERO,
                            attestation_validator: alloy::primitives::Address::ZERO,
                            operator_registry: alloy::primitives::Address::ZERO,
                            operator_state_retriever: alloy::primitives::Address::ZERO,
                            bls_apk_registry: alloy::primitives::Address::ZERO,
                            index_registry: alloy::primitives::Address::ZERO,
                            stake_registry: alloy::primitives::Address::ZERO,
                            socket_registry: alloy::primitives::Address::ZERO,
                            strategy: alloy::primitives::Address::ZERO,
                        },
                        eigenlayer: EigenlayerContractsConfig {
                            delegation_manager: alloy::primitives::Address::ZERO,
                            avs_directory: alloy::primitives::Address::ZERO,
                            strategy_manager: alloy::primitives::Address::ZERO,
                            allocation_manager: alloy::primitives::Address::ZERO,
                            rewards_coordinator: alloy::primitives::Address::ZERO,
                            strategy_factory: alloy::primitives::Address::ZERO,
                            permission_controller: alloy::primitives::Address::ZERO,
                            operator_table_updater: alloy::primitives::Address::ZERO,
                            ecdsa_certificate_verifier: alloy::primitives::Address::ZERO,
                            bn254_certificate_verifier: alloy::primitives::Address::ZERO,
                        },
                        policy: NewtonPolicyContractsConfig {
                            policy_factory: alloy::primitives::Address::ZERO,
                            policy_data_factory: alloy::primitives::Address::ZERO,
                        },
                    }
                });
                // Construct config with loaded contracts and default service config
                newton_prover_core::config::NewtonAvsConfig {
                    env: env.clone(),
                    chain_id,
                    source_chain_id: None,
                    rpc: RpcProviderConfig::load(chain_id).unwrap_or(RpcProviderConfig {
                        http: "http://127.0.0.1:8545".to_string(),
                        ws: "ws://127.0.0.1:8545".to_string(),
                    }),
                    ipfs: IpfsConfig::default(),
                    contracts,
                    service,
                }
            }
        }
    }

    /// Load config, returning an error if it fails
    /// This is for commands that actually need the config
    fn load_config_required(
        &self,
        chain_id: u64,
    ) -> eyre::Result<newton_prover_core::config::NewtonAvsConfig<NewtonCliConfig>> {
        let mut builder = NewtonAvsConfigBuilder::new(chain_id);
        if let Some(config_path) = self.resolve_config_path() {
            info!("Loading cli config from: {:?}", config_path);
            builder = builder.with_service_path(config_path);
        }
        builder.build::<NewtonCliConfig>().map_err(|e| {
            eyre::eyre!(
                "Failed to load configuration: {}. \
                Make sure deployment files exist for chain_id {} or set DEPLOYMENT_ENV environment variable.",
                e,
                chain_id
            )
        })
    }

    /// execute the configured cli command.
    pub fn run(self) -> eyre::Result<()> {
        // Build log filter - suppress verbose config loading logs by default
        let env_filter = "warn,newton_cli=info".to_string();

        // Initialize logging with specified format
        init_logger(LoggerConfig::new(self.log_format).with_env_filter(env_filter));

        let runner = NewtonRunner::default();

        // Commands that don't need config at all (all subcommands use _config)
        // These can work with a dummy config if loading fails
        let commands_that_dont_need_config =
            matches!(&self.command, Commands::PolicyFiles(_) | Commands::PolicyClient(_));

        // Determine chain_id - optional for commands that don't need config
        let chain_id = if commands_that_dont_need_config {
            self.chain_id.unwrap_or(1)
        } else {
            self.chain_id.ok_or_else(|| eyre::eyre!("chain id is required"))?
        };

        // Load config - graceful fallback only for commands that truly don't need it
        // Note: Policy and PolicyData have Simulate subcommands that need real config (for execute_wasm),
        // so they require real config even though Deploy subcommands don't use it
        // With bundled deployment files, config loading should succeed, but we allow fallback
        // for commands that don't use config in case service config file is missing
        let config = if commands_that_dont_need_config {
            self.load_config_or_minimal(chain_id)
        } else {
            // Task, Policy, and PolicyData require real config
            self.load_config_required(chain_id)?
        };

        match self.command {
            Commands::Task(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
            Commands::PolicyData(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
            Commands::Policy(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
            Commands::PolicyFiles(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
            Commands::PolicyClient(command) => runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?,
            Commands::NewtonDashboard(command) => {
                runner.run_blocking_until_ctrl_c(Box::new(command).execute(config))?
            }
        }

        Ok(())
    }
}

/// commands to be executed
#[derive(Debug, Subcommand)]
pub enum Commands {
    /// task commands
    Task(TaskCommand),

    /// policy data commands
    PolicyData(PolicyDataCommand),

    /// policy commands
    Policy(PolicyCommand),

    /// policy files commands
    PolicyFiles(PolicyFilesCommand),

    /// policy client commands
    #[command(name = "policy-client")]
    PolicyClient(PolicyClientCommand),

    /// newton dashboard commands
    #[command(name = "newton-dashboard")]
    NewtonDashboard(NewtonDashboardCommand),
}
