use std::path::PathBuf;

use anyhow::Result;
use clap::{Parser, Subcommand, ValueEnum};
use tracing_subscriber::EnvFilter;

mod chainfire;
mod power;
mod remote;

/// Deployer control CLI for UltraCloud.
///
/// - 初回ブートストラップ時に Chainfire 上の Cluster/Node/Service 定義を作成
/// - 既存の Deployer クラスタに対して宣言的な設定を apply する
/// - Deployer が壊れた場合でも、Chainfire 上の状態を直接修復できることを目標とする
#[derive(Parser, Debug)]
#[command(author, version, about)]
struct Cli {
    /// Chainfire API エンドポイント (例: http://127.0.0.1:7000)
    #[arg(long, global = true, default_value = "http://127.0.0.1:7000")]
    chainfire_endpoint: String,

    /// UltraCloud Cluster ID (論理名)
    #[arg(long, global = true)]
    cluster_id: Option<String>,

    /// UltraCloud cluster namespace (default: ultracloud)
    #[arg(long, global = true, default_value = "ultracloud")]
    cluster_namespace: String,

    /// Deployer namespace used for machine_id -> NodeConfig bootstrap mappings
    #[arg(long, global = true, default_value = "deployer")]
    deployer_namespace: String,

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

#[derive(Subcommand, Debug)]
enum Command {
    /// 初回ブートストラップ用の基本オブジェクトを作成する
    ///
    /// - Cluster メタデータ
    /// - Node 情報 (ローカルノード1台分)
    /// - 必要であれば Service/ServiceInstance のシード
    Bootstrap {
        /// ブートストラップ用のJSON/YAML設定ファイル
        #[arg(long)]
        config: PathBuf,
    },

    /// 宣言的な UltraCloud クラスタ設定を Chainfire に apply する (GitOps 的に利用可能)
    Apply {
        /// Cluster/Node/Service/Instance/MTLSPolicy を含むJSON/YAML
        #[arg(long)]
        config: PathBuf,

        /// 既存エントリを pruning するかどうか
        #[arg(long, default_value_t = false)]
        prune: bool,
    },

    /// Chainfire 上の UltraCloud 関連キーをダンプする (デバッグ用途)
    Dump {
        /// ダンプ対象の prefix (未指定の場合は cluster-namespace を使用)
        #[arg(long, default_value = "")]
        prefix: String,

        /// 出力形式
        #[arg(long, value_enum, default_value_t = DumpFormat::Text)]
        format: DumpFormat,
    },

    /// Deployer HTTP API を経由して、クラスタ状態を同期・確認する
    ///
    /// 現時点ではプレースホルダであり、将来的なGitOps連携を見据えた形だけ用意する。
    Deployer {
        /// Deployer HTTP エンドポイント (例: http://deployer.local:8080)
        #[arg(long)]
        endpoint: String,

        /// 一旦は `status` のみをサポート
        #[arg(long, default_value = "status")]
        action: String,
    },

    /// ノード単位の inventory / lifecycle 状態を確認・更新する
    Node {
        #[command(subcommand)]
        command: NodeCommand,
    },

    /// HostDeployment rollout object を確認・操作する
    Deployment {
        #[command(subcommand)]
        command: DeploymentCommand,
    },

    /// Service spec/status/publication/instances を表示する
    Service {
        #[command(subcommand)]
        command: ServiceCommand,
    },
}

#[derive(Subcommand, Debug)]
enum NodeCommand {
    /// 指定ノードの記録と関連 state を表示する
    Inspect {
        #[arg(long)]
        node_id: String,

        #[arg(long, default_value_t = false)]
        include_desired_system: bool,

        #[arg(long, default_value_t = false)]
        include_observed_system: bool,

        #[arg(long, value_enum, default_value_t = DumpFormat::Json)]
        format: DumpFormat,
    },

    /// 指定ノードの lifecycle / commissioning 状態を更新する
    SetState {
        #[arg(long)]
        node_id: String,

        #[arg(long, value_enum)]
        state: Option<NodeLifecycleStateArg>,

        #[arg(long, value_enum)]
        commission_state: Option<CommissionStateArg>,

        #[arg(long, value_enum)]
        install_state: Option<InstallStateArg>,

        #[arg(long, value_enum)]
        power_state: Option<PowerStateArg>,

        #[arg(long)]
        bmc_ref: Option<String>,
    },

    /// 指定ノードの observed-system を更新する
    SetObserved {
        #[arg(long)]
        node_id: String,

        #[arg(long)]
        status: Option<String>,

        #[arg(long)]
        nixos_configuration: Option<String>,

        #[arg(long)]
        target_system: Option<String>,

        #[arg(long)]
        current_system: Option<String>,

        #[arg(long)]
        configured_system: Option<String>,

        #[arg(long)]
        booted_system: Option<String>,

        #[arg(long)]
        rollback_system: Option<String>,
    },

    /// 指定ノードの電源操作を行う
    Power {
        #[arg(long)]
        node_id: String,

        #[arg(long, value_enum)]
        action: PowerActionArg,
    },

    /// 指定ノードに再インストールを要求する
    Reinstall {
        #[arg(long)]
        node_id: String,

        #[arg(long, default_value_t = false)]
        power_cycle: bool,
    },
}

#[derive(Subcommand, Debug)]
enum DeploymentCommand {
    /// HostDeployment の spec/status を表示する
    Inspect {
        #[arg(long)]
        name: String,

        #[arg(long, value_enum, default_value_t = DumpFormat::Json)]
        format: DumpFormat,
    },

    /// HostDeployment を一時停止する
    Pause {
        #[arg(long)]
        name: String,
    },

    /// HostDeployment を再開する
    Resume {
        #[arg(long)]
        name: String,
    },

    /// HostDeployment を中止し、配布済み desired-system を取り消す
    Abort {
        #[arg(long)]
        name: String,
    },
}

#[derive(Subcommand, Debug)]
enum ServiceCommand {
    /// Service の spec/status/publication を表示する
    Inspect {
        #[arg(long)]
        name: String,

        #[arg(long, default_value_t = false)]
        include_instances: bool,

        #[arg(long, value_enum, default_value_t = DumpFormat::Json)]
        format: DumpFormat,
    },
}

#[derive(Clone, Copy, Debug, ValueEnum)]
enum DumpFormat {
    Text,
    Json,
}

#[derive(Clone, Copy, Debug, ValueEnum)]
enum NodeLifecycleStateArg {
    Pending,
    Provisioning,
    Active,
    Failed,
    Draining,
}

impl NodeLifecycleStateArg {
    fn as_str(self) -> &'static str {
        match self {
            Self::Pending => "pending",
            Self::Provisioning => "provisioning",
            Self::Active => "active",
            Self::Failed => "failed",
            Self::Draining => "draining",
        }
    }
}

#[derive(Clone, Copy, Debug, ValueEnum)]
enum CommissionStateArg {
    Discovered,
    Commissioning,
    Commissioned,
}

impl CommissionStateArg {
    fn as_str(self) -> &'static str {
        match self {
            Self::Discovered => "discovered",
            Self::Commissioning => "commissioning",
            Self::Commissioned => "commissioned",
        }
    }
}

#[derive(Clone, Copy, Debug, ValueEnum)]
enum InstallStateArg {
    Pending,
    Installing,
    Installed,
    Failed,
    ReinstallRequested,
}

impl InstallStateArg {
    fn as_str(self) -> &'static str {
        match self {
            Self::Pending => "pending",
            Self::Installing => "installing",
            Self::Installed => "installed",
            Self::Failed => "failed",
            Self::ReinstallRequested => "reinstall_requested",
        }
    }
}

#[derive(Clone, Copy, Debug, ValueEnum)]
enum PowerStateArg {
    On,
    Off,
    Cycling,
    Unknown,
}

impl PowerStateArg {
    fn as_str(self) -> &'static str {
        match self {
            Self::On => "on",
            Self::Off => "off",
            Self::Cycling => "cycling",
            Self::Unknown => "unknown",
        }
    }
}

#[derive(Clone, Copy, Debug, ValueEnum)]
enum PowerActionArg {
    On,
    Off,
    Cycle,
    Refresh,
}

impl PowerActionArg {
    fn as_str(self) -> &'static str {
        match self {
            Self::On => "on",
            Self::Off => "off",
            Self::Cycle => "cycle",
            Self::Refresh => "refresh",
        }
    }
}

#[tokio::main]
async fn main() -> Result<()> {
    let env_filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info"));
    tracing_subscriber::fmt()
        .with_writer(std::io::stderr)
        .with_env_filter(env_filter)
        .init();

    let cli = Cli::parse();

    match cli.command {
        Command::Bootstrap { config } => {
            chainfire::bootstrap_cluster(
                &cli.chainfire_endpoint,
                &cli.cluster_namespace,
                &cli.deployer_namespace,
                cli.cluster_id.as_deref(),
                &config,
            )
            .await?;
        }
        Command::Apply { config, prune } => {
            chainfire::apply_cluster_state(
                &cli.chainfire_endpoint,
                &cli.cluster_namespace,
                &cli.deployer_namespace,
                cli.cluster_id.as_deref(),
                &config,
                prune,
            )
            .await?;
        }
        Command::Dump { prefix, format } => {
            let resolved_prefix = if prefix.trim().is_empty() {
                format!("{}/", cli.cluster_namespace)
            } else {
                prefix
            };
            chainfire::dump_prefix(
                &cli.chainfire_endpoint,
                &resolved_prefix,
                matches!(format, DumpFormat::Json),
            )
            .await?;
        }
        Command::Deployer { endpoint, action } => {
            remote::run_deployer_command(&endpoint, &action).await?;
        }
        Command::Node { command } => {
            let cluster_id = cli
                .cluster_id
                .as_deref()
                .ok_or_else(|| anyhow::anyhow!("--cluster-id is required for node commands"))?;

            match command {
                NodeCommand::Inspect {
                    node_id,
                    include_desired_system,
                    include_observed_system,
                    format,
                } => {
                    chainfire::inspect_node(
                        &cli.chainfire_endpoint,
                        &cli.cluster_namespace,
                        cluster_id,
                        &node_id,
                        include_desired_system,
                        include_observed_system,
                        matches!(format, DumpFormat::Json),
                    )
                    .await?;
                }
                NodeCommand::SetState {
                    node_id,
                    state,
                    commission_state,
                    install_state,
                    power_state,
                    bmc_ref,
                } => {
                    chainfire::set_node_states(
                        &cli.chainfire_endpoint,
                        &cli.cluster_namespace,
                        cluster_id,
                        &node_id,
                        state.map(|value| value.as_str().to_string()),
                        commission_state.map(|value| value.as_str().to_string()),
                        install_state.map(|value| value.as_str().to_string()),
                        power_state.map(|value| value.as_str().to_string()),
                        bmc_ref,
                    )
                    .await?;
                }
                NodeCommand::SetObserved {
                    node_id,
                    status,
                    nixos_configuration,
                    target_system,
                    current_system,
                    configured_system,
                    booted_system,
                    rollback_system,
                } => {
                    chainfire::set_observed_system(
                        &cli.chainfire_endpoint,
                        &cli.cluster_namespace,
                        cluster_id,
                        &node_id,
                        status,
                        nixos_configuration,
                        target_system,
                        current_system,
                        configured_system,
                        booted_system,
                        rollback_system,
                    )
                    .await?;
                }
                NodeCommand::Power { node_id, action } => {
                    power::power_node(
                        &cli.chainfire_endpoint,
                        &cli.cluster_namespace,
                        cluster_id,
                        &node_id,
                        action.as_str(),
                    )
                    .await?;
                }
                NodeCommand::Reinstall {
                    node_id,
                    power_cycle,
                } => {
                    power::request_reinstall(
                        &cli.chainfire_endpoint,
                        &cli.cluster_namespace,
                        cluster_id,
                        &node_id,
                        power_cycle,
                    )
                    .await?;
                }
            }
        }
        Command::Deployment { command } => {
            let cluster_id = cli.cluster_id.as_deref().ok_or_else(|| {
                anyhow::anyhow!("--cluster-id is required for deployment commands")
            })?;

            match command {
                DeploymentCommand::Inspect { name, format } => {
                    chainfire::inspect_host_deployment(
                        &cli.chainfire_endpoint,
                        &cli.cluster_namespace,
                        cluster_id,
                        &name,
                        matches!(format, DumpFormat::Json),
                    )
                    .await?;
                }
                DeploymentCommand::Pause { name } => {
                    chainfire::set_host_deployment_paused(
                        &cli.chainfire_endpoint,
                        &cli.cluster_namespace,
                        cluster_id,
                        &name,
                        true,
                    )
                    .await?;
                }
                DeploymentCommand::Resume { name } => {
                    chainfire::set_host_deployment_paused(
                        &cli.chainfire_endpoint,
                        &cli.cluster_namespace,
                        cluster_id,
                        &name,
                        false,
                    )
                    .await?;
                }
                DeploymentCommand::Abort { name } => {
                    chainfire::abort_host_deployment(
                        &cli.chainfire_endpoint,
                        &cli.cluster_namespace,
                        cluster_id,
                        &name,
                    )
                    .await?;
                }
            }
        }
        Command::Service { command } => {
            let cluster_id = cli
                .cluster_id
                .as_deref()
                .ok_or_else(|| anyhow::anyhow!("--cluster-id is required for service commands"))?;

            match command {
                ServiceCommand::Inspect {
                    name,
                    include_instances,
                    format,
                } => {
                    chainfire::inspect_service(
                        &cli.chainfire_endpoint,
                        &cli.cluster_namespace,
                        cluster_id,
                        &name,
                        include_instances,
                        matches!(format, DumpFormat::Json),
                    )
                    .await?;
                }
            }
        }
    }

    Ok(())
}
