//! `prvng-backup` — entry point for the backup-manager binary.
//!
//! Subcommands map to the four modes described in the crate-level docs.
//! This first cut implements the `policy` subcommand end-to-end (validate
//! and render Nickel-exported policies); the other modes are stubs whose
//! signatures and CLI surface are wired so the contract is visible to users
//! and downstream code while the implementations land in subsequent commits.

use std::path::{Path, PathBuf};
use std::process::ExitCode;

use clap::{Args, Parser, Subcommand};

use backup_manager::modes::policy_cmds;

/// CLI for the backup-manager.
#[derive(Debug, Parser)]
#[command(
    name = "prvng-backup",
    version,
    about = "Multi-context backup orchestrator (restic/kopia, ontoref-integrated)"
)]
struct Cli {
    /// Path to a configuration file. When absent, the standard hierarchy
    /// (`~/.config/provisioning/platform/config/backup-manager.ncl`) is used.
    #[arg(short, long, env = "BACKUP_CONFIG")]
    config: Option<PathBuf>,

    /// Subcommand selecting the mode of operation.
    #[command(subcommand)]
    command: Mode,
}

/// Top-level mode dispatch.
#[derive(Debug, Subcommand)]
enum Mode {
    /// Render and validate Nickel policy declarations.
    Policy(PolicyArgs),
    /// One-shot operation invoked by a scheduler (cron, systemd timer).
    OneShot(OneShotArgs),
    /// Long-running daemon orchestrator.
    Daemon(DaemonArgs),
    /// Standalone workstation mode (mount/restore/list without cluster).
    Standalone(StandaloneArgs),
    /// BackupGroup coordinator.
    Coordinator(CoordinatorArgs),
}

// ── policy ──────────────────────────────────────────────────────────────────

#[derive(Debug, Args)]
struct PolicyArgs {
    /// Sub-action to perform.
    #[command(subcommand)]
    action: PolicyAction,
}

#[derive(Debug, Subcommand)]
enum PolicyAction {
    /// Validate one or more Nickel policy files (export + deserialise).
    Validate {
        /// Files or directories to validate.
        #[arg(required = true)]
        paths: Vec<PathBuf>,

        /// Nickel `--import-path` value.
        #[arg(long, default_value = "provisioning")]
        import_path: String,
    },
    /// Render a policy file to JSON on stdout.
    Render {
        /// Policy file to render.
        path: PathBuf,
        /// Nickel `--import-path` value.
        #[arg(long, default_value = "provisioning")]
        import_path: String,
    },
}

// ── one-shot ────────────────────────────────────────────────────────────────

#[derive(Debug, Args)]
struct OneShotArgs {
    /// Operation to perform.
    #[command(subcommand)]
    op: OneShotOp,
}

#[derive(Debug, Subcommand)]
enum OneShotOp {
    /// Run a backup for a component, group, or system target.
    Backup {
        /// Target identifier.
        target: String,
        /// Optional scope filter.
        #[arg(long)]
        scope: Option<String>,
    },
    /// Restore a snapshot to a directory.
    Restore {
        /// Target identifier.
        target: String,
        /// Snapshot ID or `latest`.
        #[arg(long)]
        snapshot: String,
        /// Restore destination directory.
        #[arg(long)]
        to: PathBuf,
    },
    /// Verify snapshots (provider-level integrity checks).
    Verify {
        /// Target identifier.
        target: String,
        /// Verify level.
        #[arg(long, default_value = "quick")]
        level: String,
    },
    /// Apply retention policy and prune old snapshots.
    Prune {
        /// Target identifier.
        target: String,
        /// Dry run (do not delete).
        #[arg(long)]
        dry_run: bool,
    },
    /// List snapshots.
    List {
        /// Target identifier.
        target: String,
        /// Optional scope filter.
        #[arg(long)]
        scope: Option<String>,
    },
}

// ── daemon ──────────────────────────────────────────────────────────────────

#[derive(Debug, Args)]
struct DaemonArgs {
    /// Daemon control action.
    #[command(subcommand)]
    action: DaemonAction,
}

#[derive(Debug, Subcommand)]
enum DaemonAction {
    /// Start the daemon (foreground).
    Start,
    /// Reload policies (sends SIGHUP equivalent through the local API).
    Reload,
    /// Drain the queue and shut down cleanly.
    Drain,
    /// Print status as JSON.
    Status,
}

// ── standalone ──────────────────────────────────────────────────────────────

#[derive(Debug, Args)]
struct StandaloneArgs {
    /// Standalone operation.
    #[command(subcommand)]
    op: StandaloneOp,
}

#[derive(Debug, Subcommand)]
enum StandaloneOp {
    /// Mount a snapshot via FUSE.
    Mount {
        /// Component name.
        component: String,
        /// Snapshot ID or `latest`.
        #[arg(long)]
        snapshot: String,
        /// Mountpoint directory.
        #[arg(long)]
        at: PathBuf,
        /// Tag filters (`key=value`).
        #[arg(long)]
        tag: Vec<String>,
    },
    /// Restore directly to a directory without going through the daemon.
    Restore {
        /// Component name.
        component: String,
        /// Snapshot ID or `latest`.
        #[arg(long)]
        snapshot: String,
        /// Restore destination directory.
        #[arg(long)]
        to: PathBuf,
    },
    /// List snapshots (no daemon).
    List {
        /// Component name.
        component: String,
        /// Optional scope filter.
        #[arg(long)]
        scope: Option<String>,
    },
}

// ── coordinator ─────────────────────────────────────────────────────────────

#[derive(Debug, Args)]
struct CoordinatorArgs {
    /// Coordinator action.
    #[command(subcommand)]
    action: CoordinatorAction,
}

#[derive(Debug, Subcommand)]
enum CoordinatorAction {
    /// Run a BackupGroup.
    GroupRun {
        /// Group name.
        name: String,
    },
    /// Restore a BackupGroup at a given timestamp.
    GroupRestore {
        /// Group name.
        name: String,
        /// RFC3339 timestamp.
        #[arg(long)]
        at: String,
        /// Restore destination directory.
        #[arg(long)]
        to: PathBuf,
    },
}

#[tokio::main]
async fn main() -> ExitCode {
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_env("BACKUP_LOG")
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")),
        )
        .init();

    let cli = Cli::parse();

    match cli.command {
        Mode::Policy(args) => run_policy(args),
        Mode::OneShot(_) | Mode::Daemon(_) | Mode::Standalone(_) | Mode::Coordinator(_) => {
            eprintln!(
                "this mode is wired in the CLI surface but its implementation \
                 lands in a subsequent commit. The `policy` subcommand is fully \
                 functional today (validate / render Nickel-exported policies)."
            );
            ExitCode::from(2)
        }
    }
}

fn run_policy(args: PolicyArgs) -> ExitCode {
    match args.action {
        PolicyAction::Validate { paths, import_path } => run_policy_validate(&paths, &import_path),
        PolicyAction::Render { path, import_path } => run_policy_render(&path, &import_path),
    }
}

fn run_policy_validate(paths: &[PathBuf], import_path: &str) -> ExitCode {
    let mut total_failures = 0usize;
    for path in paths {
        total_failures += validate_path(path, import_path);
    }

    if total_failures == 0 {
        ExitCode::SUCCESS
    } else {
        eprintln!("{} validation failure(s)", total_failures);
        ExitCode::from(1)
    }
}

fn validate_path(path: &Path, import_path: &str) -> usize {
    if path.is_dir() {
        validate_directory(path, import_path)
    } else {
        let v = policy_cmds::validate_policy(path, import_path);
        print_validation(&v);
        usize::from(!v.ok)
    }
}

fn validate_directory(path: &Path, import_path: &str) -> usize {
    let entries = match std::fs::read_dir(path) {
        Ok(e) => e,
        Err(e) => {
            eprintln!("could not read directory {:?}: {}", path, e);
            return 1;
        }
    };

    entries
        .flatten()
        .filter_map(|entry| {
            let p = entry.path();
            if p.extension().is_some_and(|e| e == "ncl") {
                Some(p)
            } else {
                None
            }
        })
        .map(|p| {
            let v = policy_cmds::validate_policy(&p, import_path);
            print_validation(&v);
            usize::from(!v.ok)
        })
        .sum()
}

fn run_policy_render(path: &Path, import_path: &str) -> ExitCode {
    let value = match policy_cmds::nickel_export_to_json(path, import_path) {
        Ok(v) => v,
        Err(e) => {
            eprintln!("{}", e);
            return ExitCode::from(1);
        }
    };

    match serde_json::to_string_pretty(&value) {
        Ok(rendered) => {
            println!("{}", rendered);
            ExitCode::SUCCESS
        }
        Err(e) => {
            eprintln!("failed to render JSON: {}", e);
            ExitCode::from(1)
        }
    }
}

fn print_validation(v: &policy_cmds::PolicyValidation) {
    if v.ok {
        println!(
            "OK    {}  components={}  backup-enabled={}",
            v.source, v.component_count, v.enabled_backup_components.len()
        );
    } else {
        println!(
            "FAIL  {}  error={}",
            v.source,
            v.error.as_deref().unwrap_or("unknown")
        );
    }
}
