use crate::app::agent_brief::{AGENT_BRIEF_HARD_MAX_SEAMS, DEFAULT_AGENT_BRIEF_MAX_SEAMS};
use crate::cli::parse::expect_value;
use std::path::PathBuf;

#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) enum AgentCommand {
    Help,
    StartHelp,
    BriefHelp,
    PacketHelp,
    VerifyHelp,
    ReceiptHelp,
    StatusHelp,
    ReviewSummaryHelp,
    Start(AgentStartOptions),
    Brief(AgentBriefOptions),
    Packet(AgentPacketOptions),
    Verify(AgentVerifyOptions),
    Receipt(AgentReceiptOptions),
    Status(AgentStatusOptions),
    ReviewSummary(AgentReviewSummaryOptions),
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct AgentStartOptions {
    pub(super) root: PathBuf,
    pub(super) seam_id: String,
    pub(super) out_dir: PathBuf,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct AgentBriefOptions {
    pub(super) root: PathBuf,
    pub(super) working_set: AgentBriefWorkingSet,
    pub(super) json: bool,
    pub(super) max_seams: usize,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct AgentPacketOptions {
    pub(super) root: PathBuf,
    pub(super) seam_id: Option<String>,
    pub(super) gap_ledger: Option<PathBuf>,
    pub(super) gap_id: Option<String>,
    pub(super) json: bool,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct AgentVerifyOptions {
    pub(super) root: PathBuf,
    pub(super) before: PathBuf,
    pub(super) after: PathBuf,
    pub(super) json: bool,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct AgentReceiptOptions {
    pub(super) root: PathBuf,
    pub(super) verify_json: PathBuf,
    pub(super) seam_id: String,
    pub(super) test_changed: Option<String>,
    pub(super) commands_run: Vec<String>,
    pub(super) json: bool,
    pub(super) out: Option<PathBuf>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct AgentStatusOptions {
    pub(super) root: PathBuf,
    pub(super) json: bool,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) struct AgentReviewSummaryOptions {
    pub(super) root: PathBuf,
    pub(super) json: bool,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub(super) enum AgentBriefWorkingSet {
    Diff(PathBuf),
    Base(String),
    Files(Vec<PathBuf>),
    SeamId(String),
}

#[derive(Clone, Debug, PartialEq, Eq)]
enum WorkingSetCandidate {
    Diff(PathBuf),
    Base(String),
    Files(Vec<PathBuf>),
    SeamId(String),
}

impl WorkingSetCandidate {
    fn into_working_set(self) -> AgentBriefWorkingSet {
        match self {
            Self::Diff(path) => AgentBriefWorkingSet::Diff(path),
            Self::Base(base) => AgentBriefWorkingSet::Base(base),
            Self::Files(paths) => AgentBriefWorkingSet::Files(paths),
            Self::SeamId(seam_id) => AgentBriefWorkingSet::SeamId(seam_id),
        }
    }
}

pub(super) fn parse_agent_args(args: &[String]) -> Result<AgentCommand, String> {
    match args.first().map(|arg| arg.as_str()) {
        None | Some("--help" | "-h") => Ok(AgentCommand::Help),
        Some("start") => parse_agent_start_command(&args[1..]),
        Some("brief") => parse_agent_brief_command(&args[1..]),
        Some("packet") => parse_agent_packet_command(&args[1..]),
        Some("verify") => parse_agent_verify_command(&args[1..]),
        Some("receipt") => parse_agent_receipt_command(&args[1..]),
        Some("status") => parse_agent_status_command(&args[1..]),
        Some("review-summary") => parse_agent_review_summary_command(&args[1..]),
        Some(other) => Err(format!(
            "unknown agent subcommand {other:?}; expected `start`, `brief`, `packet`, `verify`, `receipt`, `status`, or `review-summary`"
        )),
    }
}

fn parse_agent_start_command(args: &[String]) -> Result<AgentCommand, String> {
    if args.iter().any(|arg| arg == "--help" || arg == "-h") {
        return Ok(AgentCommand::StartHelp);
    }
    parse_agent_start_options(args).map(AgentCommand::Start)
}

fn parse_agent_brief_command(args: &[String]) -> Result<AgentCommand, String> {
    if args.iter().any(|arg| arg == "--help" || arg == "-h") {
        return Ok(AgentCommand::BriefHelp);
    }
    parse_agent_brief_options(args).map(AgentCommand::Brief)
}

fn parse_agent_packet_command(args: &[String]) -> Result<AgentCommand, String> {
    if args.iter().any(|arg| arg == "--help" || arg == "-h") {
        return Ok(AgentCommand::PacketHelp);
    }
    parse_agent_packet_options(args).map(AgentCommand::Packet)
}

fn parse_agent_verify_command(args: &[String]) -> Result<AgentCommand, String> {
    if args.iter().any(|arg| arg == "--help" || arg == "-h") {
        return Ok(AgentCommand::VerifyHelp);
    }
    parse_agent_verify_options(args).map(AgentCommand::Verify)
}

fn parse_agent_receipt_command(args: &[String]) -> Result<AgentCommand, String> {
    if args.iter().any(|arg| arg == "--help" || arg == "-h") {
        return Ok(AgentCommand::ReceiptHelp);
    }
    parse_agent_receipt_options(args).map(AgentCommand::Receipt)
}

fn parse_agent_status_command(args: &[String]) -> Result<AgentCommand, String> {
    if args.iter().any(|arg| arg == "--help" || arg == "-h") {
        return Ok(AgentCommand::StatusHelp);
    }
    parse_agent_status_options(args).map(AgentCommand::Status)
}

fn parse_agent_review_summary_command(args: &[String]) -> Result<AgentCommand, String> {
    if args.iter().any(|arg| arg == "--help" || arg == "-h") {
        return Ok(AgentCommand::ReviewSummaryHelp);
    }
    parse_agent_review_summary_options(args).map(AgentCommand::ReviewSummary)
}

pub(super) fn parse_agent_start_options(args: &[String]) -> Result<AgentStartOptions, String> {
    let mut root = PathBuf::from(".");
    let mut seam_id: Option<String> = None;
    let mut out_dir = PathBuf::from("target/ripr/workflow");

    let mut i = 0usize;
    while i < args.len() {
        match args[i].as_str() {
            "--root" => {
                i += 1;
                root = PathBuf::from(expect_value(args, i, "--root")?);
            }
            "--seam-id" => {
                i += 1;
                let value = expect_value(args, i, "--seam-id")?;
                if value.trim().is_empty() {
                    return Err("agent start --seam-id requires a non-empty ID".to_string());
                }
                seam_id = Some(value.to_string());
            }
            "--out" => {
                i += 1;
                let value = expect_value(args, i, "--out")?;
                if value.trim().is_empty() {
                    return Err("agent start --out requires a non-empty path".to_string());
                }
                out_dir = PathBuf::from(value);
            }
            other => return Err(format!("unknown agent start argument {other:?}")),
        }
        i += 1;
    }

    Ok(AgentStartOptions {
        root,
        seam_id: seam_id.ok_or_else(|| "agent start requires --seam-id".to_string())?,
        out_dir,
    })
}

pub(super) fn parse_agent_brief_options(args: &[String]) -> Result<AgentBriefOptions, String> {
    let mut root = PathBuf::from(".");
    let mut working_set: Option<WorkingSetCandidate> = None;
    let mut json = false;
    let mut max_seams = DEFAULT_AGENT_BRIEF_MAX_SEAMS;

    let mut i = 0usize;
    while i < args.len() {
        match args[i].as_str() {
            "--root" => {
                i += 1;
                root = PathBuf::from(expect_value(args, i, "--root")?);
            }
            "--diff" => {
                i += 1;
                set_working_set(
                    &mut working_set,
                    WorkingSetCandidate::Diff(PathBuf::from(expect_value(args, i, "--diff")?)),
                )?;
            }
            "--base" => {
                i += 1;
                set_working_set(
                    &mut working_set,
                    WorkingSetCandidate::Base(expect_value(args, i, "--base")?.to_string()),
                )?;
            }
            "--files" => {
                i += 1;
                set_working_set(
                    &mut working_set,
                    WorkingSetCandidate::Files(parse_files_value(expect_value(
                        args, i, "--files",
                    )?)?),
                )?;
            }
            "--seam-id" => {
                i += 1;
                set_working_set(
                    &mut working_set,
                    WorkingSetCandidate::SeamId(expect_value(args, i, "--seam-id")?.to_string()),
                )?;
            }
            "--json" => json = true,
            "--max-seams" => {
                i += 1;
                max_seams = parse_max_seams(expect_value(args, i, "--max-seams")?)?;
            }
            other => return Err(format!("unknown agent brief argument {other:?}")),
        }
        i += 1;
    }

    if !json {
        return Err("agent brief requires --json until human output is implemented".to_string());
    }

    let working_set = working_set
        .ok_or_else(|| {
            "agent brief requires exactly one of --diff, --base, --files, or --seam-id".to_string()
        })?
        .into_working_set();

    Ok(AgentBriefOptions {
        root,
        working_set,
        json,
        max_seams,
    })
}

pub(super) fn parse_agent_packet_options(args: &[String]) -> Result<AgentPacketOptions, String> {
    let mut root = PathBuf::from(".");
    let mut seam_id: Option<String> = None;
    let mut gap_ledger: Option<PathBuf> = None;
    let mut gap_id: Option<String> = None;
    let mut json = false;

    let mut i = 0usize;
    while i < args.len() {
        match args[i].as_str() {
            "--root" => {
                i += 1;
                root = PathBuf::from(expect_value(args, i, "--root")?);
            }
            "--seam-id" => {
                i += 1;
                let value = expect_value(args, i, "--seam-id")?;
                if value.trim().is_empty() {
                    return Err("agent packet --seam-id requires a non-empty ID".to_string());
                }
                seam_id = Some(value.to_string());
            }
            "--gap-ledger" => {
                i += 1;
                let value = expect_value(args, i, "--gap-ledger")?;
                if value.trim().is_empty() {
                    return Err("agent packet --gap-ledger requires a non-empty path".to_string());
                }
                gap_ledger = Some(PathBuf::from(value));
            }
            "--gap-id" => {
                i += 1;
                let value = expect_value(args, i, "--gap-id")?;
                if value.trim().is_empty() {
                    return Err("agent packet --gap-id requires a non-empty ID".to_string());
                }
                gap_id = Some(value.to_string());
            }
            "--json" => json = true,
            other => return Err(format!("unknown agent packet argument {other:?}")),
        }
        i += 1;
    }

    if !json {
        return Err("agent packet requires --json until human output is implemented".to_string());
    }
    if seam_id.is_some() && (gap_ledger.is_some() || gap_id.is_some()) {
        return Err(
            "agent packet accepts either --seam-id or --gap-ledger with --gap-id, not both"
                .to_string(),
        );
    }
    match (seam_id.is_some(), gap_ledger.is_some(), gap_id.is_some()) {
        (true, false, false) | (false, true, true) => {}
        (false, true, false) => {
            return Err("agent packet --gap-ledger requires --gap-id".to_string());
        }
        (false, false, true) => {
            return Err("agent packet --gap-id requires --gap-ledger".to_string());
        }
        (false, false, false) => {
            return Err(
                "agent packet requires --seam-id or --gap-ledger with --gap-id".to_string(),
            );
        }
        _ => {}
    }

    Ok(AgentPacketOptions {
        root,
        seam_id,
        gap_ledger,
        gap_id,
        json,
    })
}

pub(super) fn parse_agent_verify_options(args: &[String]) -> Result<AgentVerifyOptions, String> {
    let mut root = PathBuf::from(".");
    let mut before: Option<PathBuf> = None;
    let mut after: Option<PathBuf> = None;
    let mut json = false;

    let mut i = 0usize;
    while i < args.len() {
        match args[i].as_str() {
            "--root" => {
                i += 1;
                root = PathBuf::from(expect_value(args, i, "--root")?);
            }
            "--before" => {
                i += 1;
                before = Some(PathBuf::from(expect_value(args, i, "--before")?));
            }
            "--after" => {
                i += 1;
                after = Some(PathBuf::from(expect_value(args, i, "--after")?));
            }
            "--json" => json = true,
            other => return Err(format!("unknown agent verify argument {other:?}")),
        }
        i += 1;
    }

    if !json {
        return Err("agent verify requires --json until human output is implemented".to_string());
    }

    Ok(AgentVerifyOptions {
        root,
        before: before.ok_or_else(|| "agent verify requires --before <path>".to_string())?,
        after: after.ok_or_else(|| "agent verify requires --after <path>".to_string())?,
        json,
    })
}

pub(super) fn parse_agent_receipt_options(args: &[String]) -> Result<AgentReceiptOptions, String> {
    let mut root = PathBuf::from(".");
    let mut verify_json: Option<PathBuf> = None;
    let mut seam_id: Option<String> = None;
    let mut test_changed: Option<String> = None;
    let mut commands_run = Vec::new();
    let mut json = false;
    let mut out: Option<PathBuf> = None;

    let mut i = 0usize;
    while i < args.len() {
        match args[i].as_str() {
            "--root" => {
                i += 1;
                root = PathBuf::from(expect_value(args, i, "--root")?);
            }
            "--verify-json" => {
                i += 1;
                verify_json = Some(PathBuf::from(expect_value(args, i, "--verify-json")?));
            }
            "--seam-id" => {
                i += 1;
                let value = expect_value(args, i, "--seam-id")?;
                if value.trim().is_empty() {
                    return Err("agent receipt --seam-id requires a non-empty ID".to_string());
                }
                seam_id = Some(value.to_string());
            }
            "--test" => {
                i += 1;
                let value = expect_value(args, i, "--test")?;
                if value.trim().is_empty() {
                    return Err("agent receipt --test requires a non-empty value".to_string());
                }
                test_changed = Some(value.to_string());
            }
            "--command" => {
                i += 1;
                let value = expect_value(args, i, "--command")?;
                if value.trim().is_empty() {
                    return Err("agent receipt --command requires a non-empty value".to_string());
                }
                commands_run.push(value.to_string());
            }
            "--json" => json = true,
            "--out" => {
                i += 1;
                out = Some(PathBuf::from(expect_value(args, i, "--out")?));
            }
            other => return Err(format!("unknown agent receipt argument {other:?}")),
        }
        i += 1;
    }

    if !json {
        return Err("agent receipt requires --json until human output is implemented".to_string());
    }

    Ok(AgentReceiptOptions {
        root,
        verify_json: verify_json
            .ok_or_else(|| "agent receipt requires --verify-json <path>".to_string())?,
        seam_id: seam_id.ok_or_else(|| "agent receipt requires --seam-id".to_string())?,
        test_changed,
        commands_run,
        json,
        out,
    })
}

pub(super) fn parse_agent_status_options(args: &[String]) -> Result<AgentStatusOptions, String> {
    let mut root = PathBuf::from(".");
    let mut json = false;

    let mut i = 0usize;
    while i < args.len() {
        match args[i].as_str() {
            "--root" => {
                i += 1;
                root = PathBuf::from(expect_value(args, i, "--root")?);
            }
            "--json" => json = true,
            other => return Err(format!("unknown agent status argument {other:?}")),
        }
        i += 1;
    }

    Ok(AgentStatusOptions { root, json })
}

pub(super) fn parse_agent_review_summary_options(
    args: &[String],
) -> Result<AgentReviewSummaryOptions, String> {
    let mut root = PathBuf::from(".");
    let mut json = false;

    let mut i = 0usize;
    while i < args.len() {
        match args[i].as_str() {
            "--root" => {
                i += 1;
                root = PathBuf::from(expect_value(args, i, "--root")?);
            }
            "--json" => json = true,
            other => return Err(format!("unknown agent review-summary argument {other:?}")),
        }
        i += 1;
    }

    Ok(AgentReviewSummaryOptions { root, json })
}

fn set_working_set(
    current: &mut Option<WorkingSetCandidate>,
    next: WorkingSetCandidate,
) -> Result<(), String> {
    if current.is_some() {
        return Err(
            "agent brief requires exactly one of --diff, --base, --files, or --seam-id".to_string(),
        );
    }
    *current = Some(next);
    Ok(())
}

fn parse_files_value(value: &str) -> Result<Vec<PathBuf>, String> {
    let mut paths = Vec::new();
    for part in value.split(',') {
        let trimmed = part.trim();
        if trimmed.is_empty() {
            return Err("agent brief --files requires non-empty paths".to_string());
        }
        paths.push(PathBuf::from(trimmed));
    }
    Ok(paths)
}

fn parse_max_seams(value: &str) -> Result<usize, String> {
    let parsed = value
        .parse::<usize>()
        .map_err(|err| format!("invalid --max-seams: expected a positive integer: {err}"))?;
    if parsed == 0 {
        return Err("invalid --max-seams: expected a positive integer".to_string());
    }
    if parsed > AGENT_BRIEF_HARD_MAX_SEAMS {
        return Err(format!(
            "invalid --max-seams: maximum is {AGENT_BRIEF_HARD_MAX_SEAMS}"
        ));
    }
    Ok(parsed)
}

#[cfg(test)]
mod tests {
    use super::*;

    fn args(values: &[&str]) -> Vec<String> {
        values.iter().map(|value| value.to_string()).collect()
    }

    #[test]
    fn agent_args_parse_help_and_brief_help() {
        assert_eq!(parse_agent_args(&args(&[])), Ok(AgentCommand::Help));
        assert_eq!(parse_agent_args(&args(&["--help"])), Ok(AgentCommand::Help));
        assert_eq!(
            parse_agent_args(&args(&["start", "--help"])),
            Ok(AgentCommand::StartHelp)
        );
        assert_eq!(
            parse_agent_args(&args(&["brief", "--help"])),
            Ok(AgentCommand::BriefHelp)
        );
        assert_eq!(
            parse_agent_args(&args(&["packet", "--help"])),
            Ok(AgentCommand::PacketHelp)
        );
        assert_eq!(
            parse_agent_args(&args(&["verify", "--help"])),
            Ok(AgentCommand::VerifyHelp)
        );
        assert_eq!(
            parse_agent_args(&args(&["receipt", "--help"])),
            Ok(AgentCommand::ReceiptHelp)
        );
        assert_eq!(
            parse_agent_args(&args(&["status", "--help"])),
            Ok(AgentCommand::StatusHelp)
        );
        assert_eq!(
            parse_agent_args(&args(&["review-summary", "--help"])),
            Ok(AgentCommand::ReviewSummaryHelp)
        );
    }

    #[test]
    fn agent_args_reject_unknown_subcommand() {
        assert_eq!(
            parse_agent_args(&args(&["packet"])),
            Err("agent packet requires --json until human output is implemented".to_string())
        );
        assert_eq!(
            parse_agent_args(&args(&["other"])),
            Err(
                "unknown agent subcommand \"other\"; expected `start`, `brief`, `packet`, `verify`, `receipt`, `status`, or `review-summary`"
                    .to_string()
            )
        );
    }

    #[test]
    fn agent_args_parse_start_request() {
        assert_eq!(
            parse_agent_args(&args(&[
                "start",
                "--root",
                "repo",
                "--seam-id",
                "f3c9e4d21a0b7c88",
                "--out",
                "target/ripr/workflow",
            ])),
            Ok(AgentCommand::Start(AgentStartOptions {
                root: PathBuf::from("repo"),
                seam_id: "f3c9e4d21a0b7c88".to_string(),
                out_dir: PathBuf::from("target/ripr/workflow"),
            }))
        );
    }

    #[test]
    fn agent_start_defaults_out_dir_and_requires_seam_id() {
        assert_eq!(
            parse_agent_start_options(&args(&["--seam-id", "f3c9e4d21a0b7c88"])),
            Ok(AgentStartOptions {
                root: PathBuf::from("."),
                seam_id: "f3c9e4d21a0b7c88".to_string(),
                out_dir: PathBuf::from("target/ripr/workflow"),
            })
        );
        assert_eq!(
            parse_agent_start_options(&args(&[])),
            Err("agent start requires --seam-id".to_string())
        );
        assert_eq!(
            parse_agent_start_options(&args(&["--seam-id", ""])),
            Err("agent start --seam-id requires a non-empty ID".to_string())
        );
    }

    #[test]
    fn agent_start_requires_values_and_rejects_unknown_arguments() {
        assert_eq!(
            parse_agent_start_options(&args(&["--root"])),
            Err("missing value for --root".to_string())
        );
        assert_eq!(
            parse_agent_start_options(&args(&["--seam-id"])),
            Err("missing value for --seam-id".to_string())
        );
        assert_eq!(
            parse_agent_start_options(&args(&["--out"])),
            Err("missing value for --out".to_string())
        );
        assert_eq!(
            parse_agent_start_options(&args(&["--seam-id", "abc", "--out", ""])),
            Err("agent start --out requires a non-empty path".to_string())
        );
        assert_eq!(
            parse_agent_start_options(&args(&["--seam-id", "abc", "--xml"])),
            Err("unknown agent start argument \"--xml\"".to_string())
        );
    }

    #[test]
    fn agent_args_parse_brief_request() {
        assert_eq!(
            parse_agent_args(&args(&["brief", "--diff", "change.diff", "--json"])),
            Ok(AgentCommand::Brief(AgentBriefOptions {
                root: PathBuf::from("."),
                working_set: AgentBriefWorkingSet::Diff(PathBuf::from("change.diff")),
                json: true,
                max_seams: DEFAULT_AGENT_BRIEF_MAX_SEAMS,
            }))
        );
    }

    #[test]
    fn agent_brief_parses_diff_scope() {
        assert_eq!(
            parse_agent_brief_options(&args(&[
                "--root",
                "repo",
                "--diff",
                "change.diff",
                "--json",
                "--max-seams",
                "2",
            ])),
            Ok(AgentBriefOptions {
                root: PathBuf::from("repo"),
                working_set: AgentBriefWorkingSet::Diff(PathBuf::from("change.diff")),
                json: true,
                max_seams: 2,
            })
        );
    }

    #[test]
    fn agent_brief_parses_base_scope() {
        assert_eq!(
            parse_agent_brief_options(&args(&["--base", "main", "--json"])),
            Ok(AgentBriefOptions {
                root: PathBuf::from("."),
                working_set: AgentBriefWorkingSet::Base("main".to_string()),
                json: true,
                max_seams: DEFAULT_AGENT_BRIEF_MAX_SEAMS,
            })
        );
    }

    #[test]
    fn agent_brief_parses_file_scope() {
        assert_eq!(
            parse_agent_brief_options(&args(&[
                "--files",
                "src/pricing.rs, tests/pricing.rs",
                "--json",
            ])),
            Ok(AgentBriefOptions {
                root: PathBuf::from("."),
                working_set: AgentBriefWorkingSet::Files(vec![
                    PathBuf::from("src/pricing.rs"),
                    PathBuf::from("tests/pricing.rs"),
                ]),
                json: true,
                max_seams: DEFAULT_AGENT_BRIEF_MAX_SEAMS,
            })
        );
    }

    #[test]
    fn agent_brief_parses_seam_id_scope() {
        assert_eq!(
            parse_agent_brief_options(&args(&["--seam-id", "f3c9e4d21a0b7c88", "--json",])),
            Ok(AgentBriefOptions {
                root: PathBuf::from("."),
                working_set: AgentBriefWorkingSet::SeamId("f3c9e4d21a0b7c88".to_string()),
                json: true,
                max_seams: DEFAULT_AGENT_BRIEF_MAX_SEAMS,
            })
        );
    }

    #[test]
    fn agent_brief_requires_json() {
        assert_eq!(
            parse_agent_brief_options(&args(&["--diff", "change.diff"])),
            Err("agent brief requires --json until human output is implemented".to_string())
        );
    }

    #[test]
    fn agent_brief_requires_exactly_one_working_set() {
        assert_eq!(
            parse_agent_brief_options(&args(&["--json"])),
            Err(
                "agent brief requires exactly one of --diff, --base, --files, or --seam-id"
                    .to_string()
            )
        );
        assert_eq!(
            parse_agent_brief_options(&args(&[
                "--diff",
                "change.diff",
                "--files",
                "src/lib.rs",
                "--json",
            ])),
            Err(
                "agent brief requires exactly one of --diff, --base, --files, or --seam-id"
                    .to_string()
            )
        );
    }

    #[test]
    fn agent_brief_requires_values_for_value_flags() {
        assert_eq!(
            parse_agent_brief_options(&args(&["--root"])),
            Err("missing value for --root".to_string())
        );
        assert_eq!(
            parse_agent_brief_options(&args(&["--diff"])),
            Err("missing value for --diff".to_string())
        );
        assert_eq!(
            parse_agent_brief_options(&args(&["--base"])),
            Err("missing value for --base".to_string())
        );
        assert_eq!(
            parse_agent_brief_options(&args(&["--files"])),
            Err("missing value for --files".to_string())
        );
        assert_eq!(
            parse_agent_brief_options(&args(&["--seam-id"])),
            Err("missing value for --seam-id".to_string())
        );
        assert_eq!(
            parse_agent_brief_options(&args(&["--max-seams"])),
            Err("missing value for --max-seams".to_string())
        );
    }

    #[test]
    fn agent_brief_rejects_invalid_limits_and_file_lists() {
        assert_eq!(
            parse_agent_brief_options(&args(&[
                "--diff",
                "change.diff",
                "--json",
                "--max-seams",
                "many",
            ])),
            Err(
                "invalid --max-seams: expected a positive integer: invalid digit found in string"
                    .to_string()
            )
        );
        assert_eq!(
            parse_agent_brief_options(&args(&[
                "--diff",
                "change.diff",
                "--json",
                "--max-seams",
                "0",
            ])),
            Err("invalid --max-seams: expected a positive integer".to_string())
        );
        assert_eq!(
            parse_agent_brief_options(&args(&[
                "--diff",
                "change.diff",
                "--json",
                "--max-seams",
                "11",
            ])),
            Err("invalid --max-seams: maximum is 10".to_string())
        );
        assert_eq!(
            parse_agent_brief_options(&args(&["--files", "src/lib.rs,", "--json"])),
            Err("agent brief --files requires non-empty paths".to_string())
        );
    }

    #[test]
    fn agent_brief_rejects_unknown_arguments() {
        assert_eq!(
            parse_agent_brief_options(&args(&["--diff", "change.diff", "--xml"])),
            Err("unknown agent brief argument \"--xml\"".to_string())
        );
    }

    #[test]
    fn agent_packet_parses_seam_id_request() {
        assert_eq!(
            parse_agent_packet_options(&args(&[
                "--root",
                "repo",
                "--seam-id",
                "f3c9e4d21a0b7c88",
                "--json",
            ])),
            Ok(AgentPacketOptions {
                root: PathBuf::from("repo"),
                seam_id: Some("f3c9e4d21a0b7c88".to_string()),
                gap_ledger: None,
                gap_id: None,
                json: true,
            })
        );
    }

    #[test]
    fn agent_packet_requires_json_and_seam_id() {
        assert_eq!(
            parse_agent_packet_options(&args(&["--seam-id", "abc"])),
            Err("agent packet requires --json until human output is implemented".to_string())
        );
        assert_eq!(
            parse_agent_packet_options(&args(&["--json"])),
            Err("agent packet requires --seam-id or --gap-ledger with --gap-id".to_string())
        );
        assert_eq!(
            parse_agent_packet_options(&args(&["--seam-id", "", "--json"])),
            Err("agent packet --seam-id requires a non-empty ID".to_string())
        );
    }

    #[test]
    fn agent_packet_rejects_unknown_arguments() {
        assert_eq!(
            parse_agent_packet_options(&args(&[
                "--seam-id",
                "f3c9e4d21a0b7c88",
                "--json",
                "--xml",
            ])),
            Err("unknown agent packet argument \"--xml\"".to_string())
        );
    }

    #[test]
    fn agent_packet_parses_gap_ledger_request() {
        assert_eq!(
            parse_agent_packet_options(&args(&[
                "--root",
                "repo",
                "--gap-ledger",
                "target/ripr/reports/gap-decision-ledger.json",
                "--gap-id",
                "gap:pr:pricing",
                "--json",
            ])),
            Ok(AgentPacketOptions {
                root: PathBuf::from("repo"),
                seam_id: None,
                gap_ledger: Some(PathBuf::from(
                    "target/ripr/reports/gap-decision-ledger.json"
                )),
                gap_id: Some("gap:pr:pricing".to_string()),
                json: true,
            })
        );
    }

    #[test]
    fn agent_packet_requires_one_identity_source() {
        assert_eq!(
            parse_agent_packet_options(&args(&[
                "--seam-id",
                "abc",
                "--gap-ledger",
                "ledger.json",
                "--gap-id",
                "gap:a",
                "--json",
            ])),
            Err(
                "agent packet accepts either --seam-id or --gap-ledger with --gap-id, not both"
                    .to_string()
            )
        );
        assert_eq!(
            parse_agent_packet_options(&args(&["--gap-ledger", "ledger.json", "--json"])),
            Err("agent packet --gap-ledger requires --gap-id".to_string())
        );
        assert_eq!(
            parse_agent_packet_options(&args(&["--gap-id", "gap:a", "--json"])),
            Err("agent packet --gap-id requires --gap-ledger".to_string())
        );
        assert_eq!(
            parse_agent_packet_options(&args(&["--gap-ledger", "", "--gap-id", "gap:a", "--json"])),
            Err("agent packet --gap-ledger requires a non-empty path".to_string())
        );
        assert_eq!(
            parse_agent_packet_options(&args(&[
                "--gap-ledger",
                "ledger.json",
                "--gap-id",
                "",
                "--json"
            ])),
            Err("agent packet --gap-id requires a non-empty ID".to_string())
        );
    }

    #[test]
    fn agent_verify_parses_before_after_request() {
        assert_eq!(
            parse_agent_verify_options(&args(&[
                "--root",
                "repo",
                "--before",
                "target/ripr/workflow/before.repo-exposure.json",
                "--after",
                "target/ripr/workflow/after.repo-exposure.json",
                "--json",
            ])),
            Ok(AgentVerifyOptions {
                root: PathBuf::from("repo"),
                before: PathBuf::from("target/ripr/workflow/before.repo-exposure.json"),
                after: PathBuf::from("target/ripr/workflow/after.repo-exposure.json"),
                json: true,
            })
        );
    }

    #[test]
    fn agent_verify_requires_json_before_and_after() {
        assert_eq!(
            parse_agent_verify_options(&args(&[
                "--before",
                "before.json",
                "--after",
                "after.json",
            ])),
            Err("agent verify requires --json until human output is implemented".to_string())
        );
        assert_eq!(
            parse_agent_verify_options(&args(&["--after", "after.json", "--json"])),
            Err("agent verify requires --before <path>".to_string())
        );
        assert_eq!(
            parse_agent_verify_options(&args(&["--before", "before.json", "--json"])),
            Err("agent verify requires --after <path>".to_string())
        );
    }

    #[test]
    fn agent_verify_rejects_unknown_arguments() {
        assert_eq!(
            parse_agent_verify_options(&args(&[
                "--before",
                "before.json",
                "--after",
                "after.json",
                "--json",
                "--format",
                "md",
            ])),
            Err("unknown agent verify argument \"--format\"".to_string())
        );
    }

    #[test]
    fn agent_receipt_parses_verify_json_request() {
        assert_eq!(
            parse_agent_receipt_options(&args(&[
                "--root",
                "repo",
                "--verify-json",
                "target/ripr/workflow/agent-verify.json",
                "--seam-id",
                "f3c9e4d21a0b7c88",
                "--test",
                "pricing_boundary",
                "--command",
                "cargo test pricing_boundary",
                "--command",
                "cargo run -p ripr -- agent verify --json",
                "--json",
                "--out",
                "target/ripr/reports/agent-receipt.json",
            ])),
            Ok(AgentReceiptOptions {
                root: PathBuf::from("repo"),
                verify_json: PathBuf::from("target/ripr/workflow/agent-verify.json"),
                seam_id: "f3c9e4d21a0b7c88".to_string(),
                test_changed: Some("pricing_boundary".to_string()),
                commands_run: vec![
                    "cargo test pricing_boundary".to_string(),
                    "cargo run -p ripr -- agent verify --json".to_string(),
                ],
                json: true,
                out: Some(PathBuf::from("target/ripr/reports/agent-receipt.json")),
            })
        );
    }

    #[test]
    fn agent_receipt_requires_json_verify_json_and_seam_id() {
        assert_eq!(
            parse_agent_receipt_options(&args(&[
                "--verify-json",
                "agent-verify.json",
                "--seam-id",
                "abc",
            ])),
            Err("agent receipt requires --json until human output is implemented".to_string())
        );
        assert_eq!(
            parse_agent_receipt_options(&args(&["--seam-id", "abc", "--json"])),
            Err("agent receipt requires --verify-json <path>".to_string())
        );
        assert_eq!(
            parse_agent_receipt_options(&args(&["--verify-json", "agent-verify.json", "--json"])),
            Err("agent receipt requires --seam-id".to_string())
        );
        assert_eq!(
            parse_agent_receipt_options(&args(&[
                "--verify-json",
                "agent-verify.json",
                "--seam-id",
                "",
                "--json",
            ])),
            Err("agent receipt --seam-id requires a non-empty ID".to_string())
        );
    }

    #[test]
    fn agent_receipt_rejects_unknown_arguments_and_empty_metadata() {
        assert_eq!(
            parse_agent_receipt_options(&args(&[
                "--verify-json",
                "agent-verify.json",
                "--seam-id",
                "abc",
                "--json",
                "--format",
                "md",
            ])),
            Err("unknown agent receipt argument \"--format\"".to_string())
        );
        assert_eq!(
            parse_agent_receipt_options(&args(&[
                "--verify-json",
                "agent-verify.json",
                "--seam-id",
                "abc",
                "--test",
                "",
                "--json",
            ])),
            Err("agent receipt --test requires a non-empty value".to_string())
        );
        assert_eq!(
            parse_agent_receipt_options(&args(&[
                "--verify-json",
                "agent-verify.json",
                "--seam-id",
                "abc",
                "--command",
                "",
                "--json",
            ])),
            Err("agent receipt --command requires a non-empty value".to_string())
        );
    }

    #[test]
    fn agent_status_parses_root_and_json() {
        assert_eq!(
            parse_agent_status_options(&args(&["--root", "repo", "--json"])),
            Ok(AgentStatusOptions {
                root: PathBuf::from("repo"),
                json: true,
            })
        );
        assert_eq!(
            parse_agent_args(&args(&["status", "--root", "repo", "--json"])),
            Ok(AgentCommand::Status(AgentStatusOptions {
                root: PathBuf::from("repo"),
                json: true,
            }))
        );
    }

    #[test]
    fn agent_status_parses_human_default_and_rejects_unknown_arguments() {
        assert_eq!(
            parse_agent_status_options(&args(&[])),
            Ok(AgentStatusOptions {
                root: PathBuf::from("."),
                json: false,
            })
        );
        assert_eq!(
            parse_agent_status_options(&args(&["--root"])),
            Err("missing value for --root".to_string())
        );
        assert_eq!(
            parse_agent_status_options(&args(&["--json", "--xml"])),
            Err("unknown agent status argument \"--xml\"".to_string())
        );
    }

    #[test]
    fn agent_review_summary_parses_root_json_and_human_default() {
        assert_eq!(
            parse_agent_review_summary_options(&args(&["--root", "repo", "--json"])),
            Ok(AgentReviewSummaryOptions {
                root: PathBuf::from("repo"),
                json: true,
            })
        );
        assert_eq!(
            parse_agent_review_summary_options(&args(&[])),
            Ok(AgentReviewSummaryOptions {
                root: PathBuf::from("."),
                json: false,
            })
        );
        assert_eq!(
            parse_agent_args(&args(&["review-summary", "--root", "repo"])),
            Ok(AgentCommand::ReviewSummary(AgentReviewSummaryOptions {
                root: PathBuf::from("repo"),
                json: false,
            }))
        );
    }

    #[test]
    fn agent_review_summary_requires_values_and_rejects_unknown_arguments() {
        assert_eq!(
            parse_agent_review_summary_options(&args(&["--root"])),
            Err("missing value for --root".to_string())
        );
        assert_eq!(
            parse_agent_review_summary_options(&args(&["--xml"])),
            Err("unknown agent review-summary argument \"--xml\"".to_string())
        );
    }
}
