use clap::Parser;
use std::path::PathBuf;

/// Log verbosity level.
#[derive(Debug, Clone, clap::ValueEnum, Default)]
pub enum LogLevel {
    /// Only error messages.
    Error,
    /// Warnings and errors.
    Warn,
    /// Informational messages, warnings, and errors.
    #[default]
    Info,
    /// Debug output and above.
    Debug,
    /// All trace output.
    Trace,
}

impl LogLevel {
    /// level string for use with `tracing-subscriber`.
    #[must_use]
    pub fn as_str(&self) -> &'static str {
        match self {
            Self::Error => "error",
            Self::Warn => "warn",
            Self::Info => "info",
            Self::Debug => "debug",
            Self::Trace => "trace",
        }
    }
}

/// SCIP indexer for `PostgreSQL` SQL.
#[derive(Parser, Debug)]
#[command(name = "psql-scip", version, about)]
pub struct Cli {
    /// Path to the schema dump (`pg_dump` --schema-only output).
    #[arg(short, long, value_name = "FILE")]
    pub schema: PathBuf,

    /// Root directory containing .sql files to index.
    #[arg(short, long, value_name = "DIR")]
    pub root: PathBuf,

    /// Output path for the generated index.scip file.
    #[arg(short, long, value_name = "FILE", default_value = "index.scip")]
    pub out: PathBuf,

    /// log verbosity; overridden by `RUST_LOG` if set
    #[arg(short, long, value_enum, default_value = "warn")]
    pub log_level: LogLevel,

    /// index files even if excluded by .gitignore
    #[arg(short, long, default_value = "false")]
    pub no_gitignore: bool,
}

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

    #[test]
    fn valid_args_populate_all_fields() {
        let cli = Cli::try_parse_from([
            "psql-scip",
            "--schema",
            "/path/to/schema.sql",
            "--root",
            "/path/to/project",
            "--out",
            "/path/to/index.scip",
        ])
        .expect("valid args must parse without error");

        assert_eq!(cli.schema, PathBuf::from("/path/to/schema.sql"));
        assert_eq!(cli.root, PathBuf::from("/path/to/project"));
        assert_eq!(cli.out, PathBuf::from("/path/to/index.scip"));
    }

    #[test]
    fn out_defaults_to_index_scip() {
        let cli = Cli::try_parse_from([
            "psql-scip",
            "--schema",
            "/path/to/schema.sql",
            "--root",
            "/path/to/project",
        ])
        .expect("valid args without --out must parse without error");

        assert_eq!(cli.out, PathBuf::from("index.scip"));
    }

    #[test]
    fn missing_required_args_returns_error() {
        let result = Cli::try_parse_from(["psql-scip"]);
        assert!(
            result.is_err(),
            "missing required args must return an error"
        );
    }

    #[test]
    fn log_level_default_is_warn() {
        let cli = Cli::try_parse_from([
            "psql-scip",
            "--schema",
            "/path/to/schema.sql",
            "--root",
            "/path/to/project",
        ])
        .expect("args must parse");

        assert_eq!(cli.log_level.as_str(), "warn");
    }

    #[test]
    fn log_level_as_str_covers_all_variants() {
        assert_eq!(LogLevel::Error.as_str(), "error");
        assert_eq!(LogLevel::Warn.as_str(), "warn");
        assert_eq!(LogLevel::Info.as_str(), "info");
        assert_eq!(LogLevel::Debug.as_str(), "debug");
        assert_eq!(LogLevel::Trace.as_str(), "trace");
    }

    #[test]
    fn no_gitignore_defaults_to_false() {
        let cli = Cli::try_parse_from([
            "psql-scip",
            "--schema",
            "/path/to/schema.sql",
            "--root",
            "/path/to/project",
        ])
        .expect("args must parse");

        assert!(!cli.no_gitignore, "no_gitignore must default to false");
    }

    #[test]
    fn no_gitignore_flag_sets_true() {
        let cli = Cli::try_parse_from([
            "psql-scip",
            "--schema",
            "/path/to/schema.sql",
            "--root",
            "/path/to/project",
            "--no-gitignore",
        ])
        .expect("args must parse");

        assert!(
            cli.no_gitignore,
            "no_gitignore must be true when flag is passed"
        );
    }
}
