//! `ontoref-tier` — thin CLI wrapper around
//! [`ontoref_daemon::tier_transition::execute`].
//!
//! Exists so the Nu surface `ore tier set <T>` can mutate `ops.tier`
//! through the single canonical Rust function (ADR-033 Hard constraint
//! `transition-tier-single-rust-function`) without spinning up the full
//! daemon. Designed for one-shot invocation per tier transition.
//!
//! The caller (typically `tier.nu`) is responsible for discovering the
//! project's current ops state and pending-migration list via the
//! `ore` dispatcher, then passing them to this binary as flags. This
//! keeps the binary focused: one input set → one transactional outcome.

use std::path::PathBuf;
use std::process::ExitCode;

use clap::{Parser, Subcommand};
use ontoref_daemon::config::{OpsConfig, Phase, Tier};
use ontoref_daemon::tier_transition::{execute, TransitionContext, TransitionError};

#[derive(Parser)]
#[command(name = "ontoref-tier", about = "Tier transition primitive (ADR-033)")]
struct Cli {
    #[command(subcommand)]
    cmd: Cmd,
}

#[derive(Subcommand)]
enum Cmd {
    /// Transition the project's `ops.tier` to the target value.
    Set {
        /// Target tier: Tier0 | Tier1 | Tier2.
        tier: String,

        /// Project root (the directory that contains `.ontoref/`).
        #[arg(long)]
        project_root: PathBuf,

        /// Current tier value declared by the project's config.
        /// One of `Tier0`, `Tier1`, `Tier2`. Pass what the caller read
        /// from `.ontoref/config.ncl::ops.tier`.
        #[arg(long)]
        current: String,

        /// Current phase value: `Progressive` or `Pure`. Defaults to
        /// `Progressive` when omitted.
        #[arg(long, default_value = "Progressive")]
        phase: String,

        /// Comma-separated list of pending migration ids (e.g.
        /// `0023,0024`). Empty when no migrations are pending.
        #[arg(long, default_value = "")]
        pending: String,

        /// Actor whose witness-signing keypair to provision on a
        /// Tier1→Tier2 ascent (ADR-036, substrate). Omitted → no key
        /// provisioning. Ignored on non-ascent transitions.
        #[arg(long)]
        actor: Option<String>,

        /// Directory for actor private keys. Defaults to
        /// `~/.config/ontoref/keys`. Only used with `--actor` on ascent.
        #[arg(long)]
        key_dir: Option<PathBuf>,

        /// Emit the outcome as JSON on stdout (machine-readable).
        #[arg(long)]
        json: bool,
    },
    /// Rotate an actor's witness-signing key (ADR-044): record an
    /// oplog-anchored succession signed by the old key, then swap the
    /// on-disk key and republish the public key. Requires `substrate`.
    RotateKey {
        /// Actor whose key to rotate.
        #[arg(long)]
        actor: String,

        /// Project root (the directory that contains `.ontoref/`).
        #[arg(long)]
        project_root: PathBuf,

        /// Directory holding actor private keys. Defaults to
        /// `~/.config/ontoref/keys`.
        #[arg(long)]
        key_dir: Option<PathBuf>,
    },
}

fn parse_tier(s: &str) -> Result<Tier, String> {
    match s {
        "Tier0" => Ok(Tier::Tier0),
        "Tier1" => Ok(Tier::Tier1),
        "Tier2" => Ok(Tier::Tier2),
        other => Err(format!(
            "unknown tier '{other}': expected Tier0 | Tier1 | Tier2"
        )),
    }
}

fn parse_phase(s: &str) -> Result<Phase, String> {
    match s {
        "Progressive" => Ok(Phase::Progressive),
        "Pure" => Ok(Phase::Pure),
        other => Err(format!(
            "unknown phase '{other}': expected Progressive | Pure"
        )),
    }
}

/// Default actor-key directory: `~/.config/ontoref/keys` (ADR-036). Falls
/// back to the current directory when `$HOME` is unset.
#[cfg(feature = "substrate")]
fn default_key_dir() -> PathBuf {
    let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_owned());
    PathBuf::from(home)
        .join(".config")
        .join("ontoref")
        .join("keys")
}

/// Unix-epoch seconds as a string, recorded verbatim as the actor's
/// `created_at` in `actors.ncl`.
#[cfg(feature = "substrate")]
fn now_unix_secs() -> String {
    use std::time::{SystemTime, UNIX_EPOCH};
    SystemTime::now()
        .duration_since(UNIX_EPOCH)
        .map(|d| d.as_secs().to_string())
        .unwrap_or_else(|_| "0".to_owned())
}

fn parse_pending(s: &str) -> Vec<String> {
    if s.is_empty() {
        return Vec::new();
    }
    s.split(',')
        .map(|x| x.trim().to_string())
        .filter(|x| !x.is_empty())
        .collect()
}

fn run() -> Result<(), String> {
    let cli = Cli::parse();
    match cli.cmd {
        Cmd::Set {
            tier,
            project_root,
            current,
            phase,
            pending,
            actor,
            key_dir,
            json,
        } => {
            let target = parse_tier(&tier)?;
            let current_tier = parse_tier(&current)?;
            let current_phase = parse_phase(&phase)?;
            let pending_migrations = parse_pending(&pending);
            // Retained for post-ascent keypair provisioning (the ctx takes
            // its own clone).
            #[cfg(not(feature = "substrate"))]
            let _ = (&actor, &key_dir);

            let ctx = TransitionContext {
                project_root: project_root.clone(),
                current_ops: OpsConfig {
                    tier: current_tier,
                    phase: current_phase,
                },
                pending_migrations,
                // CLI key-wiring is a follow-up; a Tier2 descent through
                // this surface currently errors with TierDescentWitness.
                // Use the ops surface (which carries the actor key) to
                // descend from Tier2 under the substrate feature.
                #[cfg(feature = "substrate")]
                descent_signing_key: None,
            };

            match execute(target, ctx) {
                Ok(outcome) => {
                    if json {
                        println!(
                            "{{\"ok\":true,\"from\":\"{:?}\",\"to\":\"{:?}\",\"phase\":\"{:?}\",\"\
                             rewritten\":{}}}",
                            outcome.from_tier,
                            outcome.to_tier,
                            outcome.phase,
                            outcome.config_rewritten
                        );
                    } else if outcome.config_rewritten {
                        println!(
                            "✓ tier transitioned: {:?} → {:?} (phase {:?})",
                            outcome.from_tier, outcome.to_tier, outcome.phase
                        );
                    } else {
                        println!("✓ already at {:?} — no change", outcome.to_tier);
                    }
                    // Provision the actor's witness-signing keypair on a
                    // Tier1→Tier2 ascent (ADR-036). Runs after the config
                    // rewrite; idempotent, so a re-run repairs a partial
                    // provisioning without regenerating an existing key.
                    #[cfg(feature = "substrate")]
                    if outcome.from_tier == Tier::Tier1 && outcome.to_tier == Tier::Tier2 {
                        if let Some(actor_id) = actor.as_deref() {
                            let kd = key_dir.unwrap_or_else(default_key_dir);
                            let actors_ncl = project_root.join(".ontoref").join("actors.ncl");
                            match ontoref_daemon::keypair::provision_actor(
                                actor_id,
                                &kd,
                                &actors_ncl,
                                &now_unix_secs(),
                            ) {
                                Ok(Some(_)) if !json => {
                                    println!("✓ provisioned witness key for actor '{actor_id}'");
                                }
                                Ok(_) => {}
                                Err(e) => return Err(format!("keypair provisioning failed: {e}")),
                            }
                        }
                    }
                    Ok(())
                }
                Err(e) => {
                    if json {
                        let kind = match &e {
                            TransitionError::GuardRejected(_) => "guard",
                            TransitionError::ConfigMissing { .. } => "config_missing",
                            TransitionError::OpsBlockAbsent { .. } => "ops_absent",
                            TransitionError::Io(_) => "io",
                            #[cfg(feature = "substrate")]
                            TransitionError::SubstrateBootstrap { .. } => "substrate_bootstrap",
                            #[cfg(feature = "substrate")]
                            TransitionError::TierDescentWitness { .. } => "tier_descent_witness",
                            #[cfg(feature = "substrate")]
                            TransitionError::CatalogVerification { .. } => "catalog_verification",
                        };
                        // Best-effort JSON; the error message may contain quotes —
                        // escape minimally.
                        let msg = e.to_string().replace('\\', "\\\\").replace('"', "\\\"");
                        println!("{{\"ok\":false,\"kind\":\"{kind}\",\"error\":\"{msg}\"}}");
                        Err(String::new()) // suppress duplicate stderr
                    } else {
                        Err(e.to_string())
                    }
                }
            }
        }
        Cmd::RotateKey {
            actor,
            project_root,
            key_dir,
        } => {
            #[cfg(feature = "substrate")]
            {
                let kd = key_dir.unwrap_or_else(default_key_dir);
                match ontoref_daemon::rotation::rotate_actor_key(
                    &actor,
                    &kd,
                    &project_root,
                    &now_unix_secs(),
                ) {
                    Ok(pk) => {
                        println!(
                            "✓ rotated key for '{actor}' → {}",
                            ontoref_daemon::keypair::public_key_hex(&pk)
                        );
                        Ok(())
                    }
                    Err(e) => Err(format!("rotate-key: {e}")),
                }
            }
            #[cfg(not(feature = "substrate"))]
            {
                let _ = (&actor, &project_root, &key_dir);
                Err("rotate-key requires the `substrate` feature".to_owned())
            }
        }
    }
}

fn main() -> ExitCode {
    match run() {
        Ok(()) => ExitCode::SUCCESS,
        Err(msg) => {
            if !msg.is_empty() {
                eprintln!("error: {msg}");
            }
            ExitCode::FAILURE
        }
    }
}
