use std::path::PathBuf;

use clap::{Parser, Subcommand};
use ops_keeper::{
    config::KeeperConfig,
    nats_client::{KeeperNats, PeekedMessage},
    pending::parse_pending,
    policy::{load_policy, PolicyMatcher},
    signer::Signer,
    PendingOp,
};
use tracing::warn;

#[derive(Parser)]
#[command(name = "keeper-cli", about = "Interactive keeper operations CLI (ADR-037)")]
struct Cli {
    #[arg(short, long, default_value = "keeper-daemon.toml")]
    config: PathBuf,

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

#[derive(Subcommand)]
enum Cmd {
    /// List pending ops waiting for a keeper signature
    List {
        #[arg(short, long, default_value_t = 20)]
        max: usize,
    },
    /// Show details of a specific pending op by jti
    Describe {
        /// JTI or subject prefix to match
        jti: String,
    },
    /// Sign a pending op (loads private key from config)
    Sign {
        /// op_type:target to match (e.g., "deploy:staging-vapora")
        target: String,
    },
    /// Show the compiled policy decision for a hypothetical op
    Simulate {
        op_type: String,
        target: String,
    },
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    tracing_subscriber::fmt()
        .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
        .init();

    let cli = Cli::parse();
    let cfg = KeeperConfig::from_toml(&cli.config)?;

    match cli.command {
        Cmd::List { max } => {
            let nats = KeeperNats::connect(&cfg).await?;
            let peeked = nats.peek_pending(max).await?;
            if peeked.is_empty() {
                println!("No pending ops in queue.");
                return Ok(());
            }
            for msg in &peeked {
                print_list_row(msg);
            }
            println!("{} pending op(s) shown (max {})", peeked.len(), max);
        }

        Cmd::Describe { jti } => {
            let nats = KeeperNats::connect(&cfg).await?;
            let peeked = nats.peek_pending(100).await?;
            let mut found = false;
            for msg in &peeked {
                found |= describe_msg(msg, &jti);
            }
            if !found {
                eprintln!("No pending op matching '{jti}' found.");
                std::process::exit(1);
            }
        }

        Cmd::Sign { target } => {
            let private_pem = std::fs::read(&cfg.private_key_path)?;
            let public_pem = std::fs::read(&cfg.public_key_path)?;
            let signer = Signer::from_pem_files(
                &private_pem,
                &public_pem,
                cfg.issuer_id.clone(),
                cfg.workspace.clone(),
                cfg.token_validity_secs,
            )?;

            let nats = KeeperNats::connect(&cfg).await?;
            let peeked = nats.peek_pending(100).await?;

            let (op_type_filter, target_filter) = target
                .split_once(':')
                .map(|(a, b)| (a.to_string(), b.to_string()))
                .unwrap_or_else(|| ("*".to_string(), target.clone()));

            let mut signed = 0usize;
            for msg in &peeked {
                let did_sign = sign_msg(msg, &signer, &nats, &op_type_filter, &target_filter).await;
                match did_sign {
                    Ok(true) => signed += 1,
                    Ok(false) => {}
                    Err(e) => return Err(e),
                }
            }
            println!("{signed} op(s) signed.");
        }

        Cmd::Simulate { op_type, target } => {
            let policy = load_policy(&cfg.policy_path)?;
            let matcher = PolicyMatcher::from_policy(&policy)?;
            let op = PendingOp {
                op_type: op_type.clone(),
                target: target.clone(),
                sub: "simulate".to_string(),
                expected_state_version: "v0".to_string(),
                image: String::new(),
                params: serde_json::Value::Null,
            };
            let decision = matcher.decide(&op);
            println!("simulate  op_type={op_type}  target={target}");
            println!("decision: {}", decision.as_str());
        }
    }

    Ok(())
}

fn print_list_row(msg: &PeekedMessage) {
    match parse_pending(&msg.payload) {
        Ok(op) => println!(
            "{:<30}  op_type={:<15}  target={:<25}  sub={}",
            msg.subject, op.op_type, op.target, op.sub
        ),
        Err(_) => println!("{:<30}  <unparseable payload>", msg.subject),
    }
}

fn describe_msg(msg: &PeekedMessage, jti: &str) -> bool {
    if !msg.subject.contains(jti) {
        return false;
    }
    match parse_pending(&msg.payload) {
        Ok(op) => {
            println!("subject:  {}", msg.subject);
            println!("op_type:  {}", op.op_type);
            println!("target:   {}", op.target);
            println!("sub:      {}", op.sub);
            println!("version:  {}", op.expected_state_version);
            println!("image:    {}", op.image);
            println!("params:   {}", op.params);
            true
        }
        Err(e) => {
            eprintln!("parse error for {}: {e}", msg.subject);
            false
        }
    }
}

async fn sign_msg(
    msg: &PeekedMessage,
    signer: &Signer,
    nats: &KeeperNats,
    op_type_filter: &str,
    target_filter: &str,
) -> Result<bool, Box<dyn std::error::Error>> {
    let op = match parse_pending(&msg.payload) {
        Ok(op) => op,
        Err(e) => {
            warn!(error = %e, "skipping unparseable message");
            return Ok(false);
        }
    };

    let op_match = op_type_filter == "*" || op_type_filter == op.op_type;
    let target_match = target_filter == "*" || op.target.contains(target_filter);
    if !op_match || !target_match {
        return Ok(false);
    }

    let jwt = signer.sign_op(&op)?;
    nats.publish_signed(&op.op_type, &jwt).await?;
    println!("signed: {} → {}", msg.subject, op.target);
    Ok(true)
}
