use std::path::PathBuf;

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

#[derive(Parser)]
#[command(
    name = "nex",
    about = "Package manager for nix-darwin, NixOS, and homebrew"
)]
#[command(version)]
pub struct Cli {
    #[command(subcommand)]
    pub command: Command,

    /// Path to the nix-darwin repo (overrides auto-discovery)
    #[arg(long, global = true, env = "NEX_REPO")]
    pub repo: Option<PathBuf>,

    /// Hostname for system rebuild (overrides auto-detect)
    #[arg(long, global = true, env = "NEX_HOSTNAME")]
    pub hostname: Option<String>,

    /// Show what would change without editing or switching
    #[arg(long, global = true)]
    pub dry_run: bool,
}

#[derive(Subcommand)]
pub enum Command {
    /// Set up nix-darwin + homebrew (macOS) or NixOS (Linux) on this machine
    Init {
        /// Clone an existing nix-darwin repo instead of scaffolding
        #[arg(long)]
        from: Option<String>,
    },
    /// Move a system-owned config (e.g. /etc/nixos) into a user-writable
    /// directory so nex no longer needs sudo to read or edit it.
    Relocate {
        /// Target path. Defaults to ~/nix-config.
        #[arg(long)]
        to: Option<PathBuf>,
    },
    /// Capture all installed brew packages into the nex config
    Adopt,
    /// Install packages
    Install {
        /// Force install as a Nix package (skip auto-resolution)
        #[arg(long, conflicts_with_all = ["cask", "brew"])]
        nix: bool,
        /// Install as a Homebrew cask (GUI app)
        #[arg(long, conflicts_with_all = ["nix", "brew"])]
        cask: bool,
        /// Install as a Homebrew formula
        #[arg(long, conflicts_with_all = ["nix", "cask"])]
        brew: bool,
        /// Resolve/write Armory package locks without fetching OCI payloads
        #[arg(long)]
        lock_only: bool,
        /// Package names
        packages: Vec<String>,
    },
    /// Remove packages
    Remove {
        /// Remove a Homebrew cask
        #[arg(long)]
        cask: bool,
        /// Remove a Homebrew formula
        #[arg(long)]
        brew: bool,
        /// Package names
        packages: Vec<String>,
    },
    /// Search nixpkgs and configured Armory registries for a package
    Search {
        /// Search query
        query: String,
    },
    /// Show Armory package metadata for <kind>/<id>
    Info {
        /// Armory package ref, e.g. profile/rust-shop
        package_ref: String,
    },
    /// List all declared packages
    List,
    /// Update flake inputs and switch
    Update,
    /// Rebuild and activate
    Switch,
    /// Rollback to previous generation
    Rollback,
    /// Try a package in an ephemeral nix shell
    Try {
        /// Package to try
        package: String,
    },
    /// Identify brew packages that can migrate to nix
    Migrate,
    /// Inspect and migrate devenv projects into Nex profiles
    Devenv {
        #[command(subcommand)]
        action: DevenvAction,
    },
    /// Inspect live host hardware and classify install-relevant devices
    Hardware {
        #[command(subcommand)]
        action: HardwareAction,
    },
    /// Inspect and validate Nex artifact directories for Armory distribution
    Artifact {
        #[command(subcommand)]
        action: ArtifactAction,
    },
    /// Inspect and validate Nex machine profiles
    MachineProfile {
        #[command(subcommand)]
        action: MachineProfileAction,
    },
    /// Inspect and validate Nex profile fragments
    ProfileFragment {
        #[command(subcommand)]
        action: ProfileFragmentAction,
    },
    /// Manage and apply machine profiles
    #[command(
        after_help = "Note: `nex profile <source>` was renamed to `nex profile apply <source>` in v0.16.0"
    )]
    Profile {
        #[command(subcommand)]
        action: ProfileAction,
    },
    /// Build a bootable NixOS installer USB, optionally with a baked-in machine profile
    Forge {
        #[command(subcommand)]
        action: Option<ForgeAction>,

        /// Nex machine profile (GitHub user/repo) to bake in. Omit for generic styx installer.
        #[arg(value_name = "MACHINE_PROFILE")]
        profile: Option<String>,

        /// Hostname default for the target system (user can override at install time)
        #[arg(long)]
        hostname: Option<String>,

        /// Target USB device to flash (e.g. /dev/sdb, /dev/disk4). If omitted, builds bundle only.
        #[arg(long)]
        disk: Option<String>,

        /// Output directory for the bundle (default: /tmp/nex-forge)
        #[arg(long, short)]
        output: Option<PathBuf>,

        /// Target architecture (x86_64 or aarch64). Prompted interactively if omitted.
        #[arg(long)]
        arch: Option<String>,
    },
    /// Interactive NixOS installer (runs on target machine after booting from USB)
    Polymerize {
        /// Path to the bundled defaults (written by nex forge). Auto-detected from USB.
        #[arg(long)]
        bundle: Option<PathBuf>,
    },
    /// Build an OCI container image from a machine profile or Styrene package manifest
    BuildImage {
        /// Nex machine profile, local machine-profile.pkl, compatibility machine-profile.toml, or styrene package manifest
        #[arg(value_name = "SOURCE")]
        source: String,

        /// Image name (default: package image name or profile name)
        #[arg(long)]
        name: Option<String>,

        /// Image tag (default: package image tag or "latest")
        #[arg(long)]
        tag: Option<String>,
    },
    /// Enter a dev shell from a flake (wraps nix develop)
    Develop {
        /// Flake reference (e.g. github:styrene-lab/nex, ., ./path/to/flake)
        #[arg(value_name = "FLAKE")]
        flake: String,
    },
    /// Open a project with omegon AI coding agent (requires omegon)
    Dev {
        /// Project flake reference or bare name (e.g. styrene-lab/nex, omegon, .)
        #[arg(value_name = "PROJECT")]
        project: String,
    },
    /// Manage local Nex configuration
    Config {
        #[command(subcommand)]
        action: ConfigAction,
    },
    /// Manage Styrene identity (key generation, display)
    Identity {
        #[command(subcommand)]
        action: IdentityAction,
    },
    /// Manage RBAC roster (sync from hub)
    Rbac {
        #[command(subcommand)]
        action: RbacAction,
    },
    /// Check and fix common configuration issues
    Doctor {
        /// Apply supported bootstrap/root repairs
        #[arg(long)]
        fix: bool,

        /// Limit fixes to one doctor scope
        #[arg(value_enum)]
        scope: Option<DoctorScope>,
    },
    /// Refresh Armory package locks
    Lock {
        #[command(subcommand)]
        action: LockAction,
    },
    /// Update nex itself to the latest release
    SelfUpdate,
    /// Preview what would change
    Diff,
    /// Garbage collect nix store
    Gc,
}

#[derive(Subcommand)]
pub enum LockAction {
    /// Re-resolve roots from the package lock
    Refresh,
    /// Materialize locked Armory packages into the local store
    Materialize,
    /// Show Armory package lock status
    Status,
}

#[derive(Clone, Copy, Debug, ValueEnum)]
pub enum DoctorScope {
    All,
    DarwinBootstrap,
    HomebrewBootstrap,
}

#[derive(Subcommand)]
pub enum ConfigAction {
    /// Export effective local config from canonical Pkl to compatibility formats
    Export {
        /// Output format
        #[arg(long, value_name = "FORMAT", default_value = "toml")]
        format: String,

        /// Write to a file instead of stdout
        #[arg(long)]
        output: Option<PathBuf>,
    },
    /// Create canonical config.pkl from existing compatibility config.toml
    Migrate {
        /// Keep or regenerate compatibility config.toml after migration
        #[arg(long)]
        keep_toml: bool,
    },
}

#[derive(Subcommand)]
pub enum ForgeAction {
    /// Plan a forge request/template without creating artifacts or flashing disks
    Plan {
        /// Canonical Pkl request/template path. JSON is accepted as transport fallback.
        #[arg(long)]
        request: PathBuf,

        /// Hardware inventory JSON from `nex hardware scan --json`
        #[arg(long)]
        inventory: Option<PathBuf>,
    },
    /// Execute a forge request after planning and safety checks
    Run {
        /// Canonical Pkl request path. JSON is accepted as transport fallback.
        #[arg(long)]
        request: PathBuf,

        /// Hardware inventory JSON from `nex hardware scan --json`
        #[arg(long)]
        inventory: Option<PathBuf>,

        /// Emit phase/blocker/artifact events as JSON lines
        #[arg(long, value_name = "FORMAT", default_value = "human")]
        events: String,
    },
    /// Check host readiness for a forge request without building or flashing
    Preflight {
        /// Canonical Pkl request path. JSON is accepted as transport fallback.
        #[arg(long)]
        request: PathBuf,

        /// Hardware inventory JSON from `nex hardware scan --json`
        #[arg(long)]
        inventory: Option<PathBuf>,

        /// Emit a stable JSON report
        #[arg(long)]
        json: bool,
    },
    /// Validate and inspect a forge template without building or flashing anything
    Check {
        /// Canonical forge.pkl template path
        #[arg(value_name = "PATH")]
        path: PathBuf,

        /// Optional Armory forge.toml metadata path to compare against the template
        #[arg(long)]
        metadata: Option<PathBuf>,

        /// Emit a stable JSON report
        #[arg(long)]
        json: bool,

        /// Explicitly require non-executing validation mode
        #[arg(long)]
        no_execute: bool,
    },
    /// Evaluate a generated NixOS materialization workspace without building or flashing
    CheckMaterialization {
        /// Directory containing a generated flake.nix workspace. If omitted, --source is required.
        #[arg(value_name = "WORKSPACE")]
        workspace: Option<PathBuf>,

        /// Canonical Pkl machine/materialization source to scaffold into a temporary workspace; TOML is compatibility/interchange.
        #[arg(long)]
        source: Option<String>,

        /// Hostname under nixosConfigurations.<hostname>
        #[arg(long)]
        hostname: String,

        /// Materialization target to evaluate deterministically
        #[arg(long, default_value = "toplevel")]
        target: String,
    },
    /// Export a composable nixosModule from a materialization source
    BuildModule {
        /// Canonical Pkl materialization source; TOML is compatibility/interchange
        #[arg(value_name = "SOURCE")]
        source: PathBuf,

        /// nixosModules.<name> output name
        #[arg(long)]
        name: String,

        /// Output directory for generated flake/module
        #[arg(long, short)]
        output: PathBuf,
    },
    /// Build a deterministic materialization output after validation
    BuildMaterialization {
        /// Canonical Pkl materialization source; TOML is compatibility/interchange
        #[arg(value_name = "SOURCE")]
        source: PathBuf,

        /// Hostname under nixosConfigurations.<hostname>
        #[arg(long)]
        hostname: String,

        /// Materialization target to build
        #[arg(long, default_value = "sd-image")]
        target: String,

        /// Output link path for nix build
        #[arg(long, short)]
        output: PathBuf,
    },
}

#[derive(Subcommand)]
pub enum IdentityAction {
    /// Generate a new Styrene identity
    Init {
        /// Path to the identity file (default: ~/.config/styrene/identity.key)
        #[arg(long)]
        path: Option<PathBuf>,
    },
    /// Display identity hash and derived public keys
    Show {
        /// Path to the identity file (default: ~/.config/styrene/identity.key)
        #[arg(long)]
        path: Option<PathBuf>,
    },
    /// Scan for all identities on this machine
    List,
    /// Export or manage SSH public keys derived from identity
    Ssh {
        /// Label for the SSH key (e.g. "github", "work")
        label: Option<String>,
        /// List all registered SSH key labels
        #[arg(long, conflicts_with = "label")]
        list: bool,
        /// Register a new SSH key label
        #[arg(long, conflicts_with_all = ["label", "list"])]
        add: Option<String>,
    },
    /// Configure git commit signing with identity
    Git {
        /// Show current git signing configuration
        #[arg(long)]
        show: bool,
    },
    /// Export WireGuard key pair derived from identity
    Wg,
    /// Export age encryption identity/recipient derived from identity
    Age,
    /// Link this identity to a Signum hub for SSO
    Link {
        /// Signum instance URL (e.g. https://signum.styrene.io)
        url: String,
        /// Enrollment invite code (if required by the hub)
        #[arg(long)]
        code: Option<String>,
        /// Path to the identity file (default: ~/.config/styrene/identity.key)
        #[arg(long)]
        path: Option<PathBuf>,
    },
}

#[derive(Subcommand)]
pub enum DevenvAction {
    /// Inspect a devenv project and emit an import report
    Inspect {
        /// Path to a devenv project directory
        #[arg(value_name = "PATH")]
        path: PathBuf,

        /// Emit stable JSON output
        #[arg(long)]
        json: bool,
    },
    /// Plan a safe migration from devenv concepts into Nex artifacts
    Plan {
        /// Path to a devenv project directory
        #[arg(value_name = "PATH")]
        path: PathBuf,

        /// Emit stable JSON output
        #[arg(long)]
        json: bool,
    },
    /// Explain how a devenv project maps into Nex concepts
    Explain {
        /// Path to a devenv project directory
        #[arg(value_name = "PATH")]
        path: PathBuf,

        /// Emit stable JSON output
        #[arg(long)]
        json: bool,
    },
    /// List or validate Nex's devenv surface-awareness catalog
    Catalog {
        #[command(subcommand)]
        action: DevenvCatalogAction,
    },
}

#[derive(Subcommand)]
pub enum DevenvCatalogAction {
    /// List the embedded Nex policy mappings for known devenv surfaces
    List {
        /// Emit stable JSON output
        #[arg(long)]
        json: bool,
    },
}

#[derive(Subcommand)]
pub enum HardwareAction {
    /// Scan this host and emit a hardware inventory
    Scan {
        /// Emit stable JSON output
        #[arg(long)]
        json: bool,

        /// Write the inventory to a file instead of stdout
        #[arg(long, short)]
        output: Option<PathBuf>,
    },
    /// Classify one disk for Forge target-attestation safety
    Attest {
        /// Disk identifier or path, e.g. disk4 or /dev/disk4
        #[arg(long)]
        disk: String,

        /// Emit stable JSON output
        #[arg(long)]
        json: bool,
    },
    /// Recommend starter profile surfaces for this host
    Match {
        /// Hardware inventory JSON from `nex hardware scan --json`; scans live host when omitted
        #[arg(long)]
        inventory: Option<PathBuf>,

        /// Desired purpose, e.g. dev, desktop, server, gaming, mesh-node
        #[arg(long)]
        purpose: Option<String>,

        /// Emit stable JSON output
        #[arg(long)]
        json: bool,
    },
}

#[derive(Subcommand)]
pub enum ArtifactAction {
    /// Validate a Nex artifact directory containing machine-profile.pkl or payload.pkl
    Check {
        /// Path to artifact directory
        #[arg(value_name = "PATH")]
        path: PathBuf,

        /// Evidence tier to validate
        #[arg(long, default_value = "evaluates")]
        evidence: String,

        /// Emit a stable JSON report
        #[arg(long)]
        json: bool,
    },
    /// Validate compatibility between a machine-profile artifact and materialization-payload artifact
    CheckRelationship {
        /// Machine-profile artifact directory
        #[arg(long)]
        profile: PathBuf,

        /// Materialization-payload artifact directory
        #[arg(long)]
        payload: PathBuf,

        /// Emit a stable JSON report
        #[arg(long)]
        json: bool,
    },
}

#[derive(Subcommand)]
pub enum MachineProfileAction {
    /// Validate a machine-profile.pkl manifest
    Validate {
        /// Path to machine-profile.pkl or a directory containing it; TOML is compatibility/interchange
        #[arg(value_name = "PATH")]
        path: PathBuf,
    },
    /// Inspect a machine-profile.pkl manifest
    Inspect {
        /// Path to machine-profile.pkl or a directory containing it; TOML is compatibility/interchange
        #[arg(value_name = "PATH")]
        path: PathBuf,

        /// Emit stable JSON metadata after validation
        #[arg(long)]
        json: bool,
    },
}

#[derive(Subcommand)]
pub enum ProfileFragmentAction {
    /// Validate a profile fragment TOML file or directory of fragments
    Validate {
        /// Path to a fragment TOML file or a directory containing fragments
        #[arg(value_name = "PATH")]
        path: PathBuf,
    },
    /// Inspect a profile fragment TOML file
    Inspect {
        /// Path to a fragment TOML file
        #[arg(value_name = "PATH")]
        path: PathBuf,

        /// Emit stable JSON metadata after validation
        #[arg(long)]
        json: bool,
    },
}

#[derive(Subcommand)]
pub enum ProfileAction {
    /// Apply a machine profile to this machine
    Apply {
        /// GitHub repo (user/repo), URL, or local path
        #[arg(value_name = "SOURCE")]
        source: String,
        /// Verify the machine profile signature before applying (local .signed.toml files only)
        #[arg(long)]
        verify: bool,
    },
    /// Sign a machine profile with your Styrene identity
    Sign {
        /// GitHub repo (user/repo) or local path to machine-profile.pkl; TOML is compatibility/interchange
        #[arg(value_name = "SOURCE")]
        source: String,
        /// Write signature to a detached .sig file instead of embedding in [meta]
        #[arg(long)]
        detached: bool,
    },
    /// Verify a signed machine profile
    Verify {
        /// GitHub repo (user/repo) or local path to machine-profile.pkl; TOML is compatibility/interchange
        #[arg(value_name = "SOURCE")]
        source: String,
    },
}

#[derive(Subcommand)]
pub enum RbacAction {
    /// Sync RBAC roster entries from a Signum hub
    Sync {
        /// Hub URL (e.g. https://signum.styrene.io)
        #[arg(value_name = "HUB_URL")]
        hub_url: String,
        /// Fetch only a specific identity's entry
        #[arg(long)]
        identity: Option<String>,
        /// Admin token for full roster access (single-identity fetch is public)
        #[arg(long, env = "SIGNUM_ADMIN_TOKEN")]
        token: Option<String>,
        /// Output path for the TOML config (default: ~/.config/styrene/config.toml)
        #[arg(long, short)]
        output: Option<PathBuf>,
    },
}
