mod finance;
mod model;
mod runtime;
mod validate;

use std::env;
use std::path::Path;

pub(crate) use model::{
    Command, CompareCommand, FinanceCommand, FixtureStateCommand, InspectCommand, RuntimeCommand,
    ScanCommand,
};

const USAGE: &str = "usage: rrt-cli [validate [repo-root] | finance eval <snapshot.json> | finance diff <left.json> <right.json> | runtime validate-fixture <fixture.json> | runtime summarize-fixture <fixture.json> | runtime export-fixture-state <fixture.json> <snapshot.json> | runtime diff-state <left.json> <right.json> | runtime summarize-state <snapshot.json> | runtime snapshot-state <input.json> <snapshot.json> | runtime inspect-smp <file.smp> | runtime inspect-candidate-table <file.smp> | runtime inspect-compact-event-dispatch-cluster <maps-dir> | runtime inspect-compact-event-dispatch-cluster-counts <maps-dir> | runtime inspect-map-title-hints <maps-dir> | runtime summarize-save-load <file.smp> | runtime load-save-slice <file.smp> | runtime inspect-save-company-chairman <file.smp> | runtime inspect-save-placed-structure-triplets <file.smp> | runtime compare-region-fixed-row-runs <left.gms> <right.gms> | runtime inspect-periodic-company-service-trace <file.smp> | runtime inspect-region-service-trace <file.smp> | runtime inspect-infrastructure-asset-trace <file.smp> | runtime inspect-save-region-queued-notice-records <file.smp> | runtime inspect-placed-structure-dynamic-side-buffer <file.smp> | runtime inspect-unclassified-save-collections <file.smp> | runtime snapshot-save-state <file.smp> <snapshot.json> | runtime export-save-slice <file.smp> <save-slice.json> | runtime export-overlay-import <snapshot.json> <save-slice.json> <overlay-import.json> | runtime inspect-pk4 <file.pk4> | runtime inspect-cargo-types <CargoTypes-dir> | runtime inspect-building-type-sources <BuildingTypes-dir> [building-bindings.json] | runtime inspect-cargo-skins <Cargo106.PK4> | runtime inspect-cargo-economy-sources <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-production-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-cargo-price-selector <CargoTypes-dir> <Cargo106.PK4> | runtime inspect-win <file.win> | runtime extract-pk4-entry <file.pk4> <entry-name> <output-path> | runtime inspect-campaign-exe <RT3.exe> | runtime compare-classic-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-105-profile <save1.gms> <save2.gms> [saveN.gms...] | runtime compare-candidate-table <file1> <file2> [fileN...] | runtime compare-recipe-book-lines <file1> <file2> [fileN...] | runtime compare-setup-payload-core <file1> <file2> [fileN...] | runtime compare-setup-launch-payload <file1> <file2> [fileN...] | runtime compare-post-special-conditions-scalars <file1> <file2> [fileN...] | runtime scan-candidate-table-headers <root-dir> | runtime scan-candidate-table-named-runs <root-dir> | runtime scan-special-conditions <root-dir> | runtime scan-aligned-runtime-rule-band <root-dir> | runtime scan-post-special-conditions-scalars <root-dir> | runtime scan-post-special-conditions-tail <root-dir> | runtime scan-recipe-book-lines <root-dir> | runtime export-profile-block <save.gms> <profile.json>]";

pub(super) fn parse_command() -> Result<Command, Box<dyn std::error::Error>> {
    let args: Vec<String> = env::args().skip(1).collect();
    let current_dir = env::current_dir()?;
    parse_command_args(&args, &current_dir)
}

fn parse_command_args(
    args: &[String],
    current_dir: &Path,
) -> Result<Command, Box<dyn std::error::Error>> {
    match args {
        [] => Ok(Command::Validate {
            repo_root: current_dir.to_path_buf(),
        }),
        [command, rest @ ..] if command == "validate" => {
            validate::parse_validate_command(rest, current_dir)
        }
        [command, rest @ ..] if command == "finance" => finance::parse_finance_command(rest),
        [command, rest @ ..] if command == "runtime" => runtime::parse_runtime_command(rest),
        _ => usage_error(),
    }
}

fn usage_error<T>() -> Result<T, Box<dyn std::error::Error>> {
    Err(USAGE.into())
}

#[cfg(test)]
mod tests {
    use std::path::PathBuf;

    use super::{
        Command, CompareCommand, FinanceCommand, FixtureStateCommand, InspectCommand,
        RuntimeCommand, ScanCommand, parse_command_args,
    };

    fn parse(args: &[&str]) -> Command {
        parse_command_args(
            &args
                .iter()
                .map(|arg| (*arg).to_string())
                .collect::<Vec<_>>(),
            PathBuf::from("/tmp/workspace").as_path(),
        )
        .expect("command should parse")
    }

    #[test]
    fn parses_validate_with_default_repo_root() {
        assert_eq!(
            parse(&[]),
            Command::Validate {
                repo_root: PathBuf::from("/tmp/workspace"),
            }
        );
    }

    #[test]
    fn parses_finance_eval() {
        assert_eq!(
            parse(&["finance", "eval", "snapshot.json"]),
            Command::Finance(FinanceCommand::Eval {
                snapshot_path: PathBuf::from("snapshot.json"),
            })
        );
    }

    #[test]
    fn parses_finance_diff() {
        assert_eq!(
            parse(&["finance", "diff", "left.json", "right.json"]),
            Command::Finance(FinanceCommand::Diff {
                left_path: PathBuf::from("left.json"),
                right_path: PathBuf::from("right.json"),
            })
        );
    }

    #[test]
    fn parses_runtime_snapshot_state_command() {
        assert_eq!(
            parse(&["runtime", "snapshot-state", "input.json", "snapshot.json"]),
            Command::Runtime(RuntimeCommand::FixtureState(
                FixtureStateCommand::SnapshotState {
                    input_path: PathBuf::from("input.json"),
                    output_path: PathBuf::from("snapshot.json"),
                }
            ))
        );
    }

    #[test]
    fn parses_runtime_snapshot_save_state_command() {
        assert_eq!(
            parse(&[
                "runtime",
                "snapshot-save-state",
                "save.gms",
                "snapshot.json"
            ]),
            Command::Runtime(RuntimeCommand::FixtureState(
                FixtureStateCommand::SnapshotSaveState {
                    smp_path: PathBuf::from("save.gms"),
                    output_path: PathBuf::from("snapshot.json"),
                }
            ))
        );
    }

    #[test]
    fn parses_runtime_inspect_command() {
        assert_eq!(
            parse(&["runtime", "inspect-campaign-exe", "RT3.exe"]),
            Command::Runtime(RuntimeCommand::Inspect(
                InspectCommand::InspectCampaignExe {
                    exe_path: PathBuf::from("RT3.exe"),
                }
            ))
        );
    }

    #[test]
    fn parses_runtime_compare_command() {
        assert_eq!(
            parse(&["runtime", "compare-candidate-table", "a.gms", "b.gms"]),
            Command::Runtime(RuntimeCommand::Compare(
                CompareCommand::CompareCandidateTable {
                    smp_paths: vec![PathBuf::from("a.gms"), PathBuf::from("b.gms")],
                }
            ))
        );
    }

    #[test]
    fn parses_runtime_scan_command() {
        assert_eq!(
            parse(&["runtime", "scan-special-conditions", "root"]),
            Command::Runtime(RuntimeCommand::Scan(ScanCommand::ScanSpecialConditions {
                root_path: PathBuf::from("root"),
            }))
        );
    }
}
