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

use onym::errors::{CliError, ErrorKind};
use onym::output::Format;
use onym::transport::ReqwestTransport;
use onym::types::{Classification, NameReport, Status};
use onym::{check_name, default_tlds, output, schema, validate};

#[derive(Parser)]
#[command(
    name = "onym",
    version,
    about = "Check name availability across package registries and domains",
    long_about = "onym checks whether a name is available on crates.io, PyPI, npm, GitHub, and common TLDs, classifying taken results (active, placeholder, dead) so you can tell real conflicts from squatters.",
    disable_help_subcommand = true,
    arg_required_else_help = true
)]
struct Cli {
    #[command(subcommand)]
    command: Command,
}

#[derive(Subcommand)]
enum Command {
    /// Check name availability across registries and domains
    Check {
        /// One or more names to check
        #[arg(required = true, value_name = "NAME")]
        names: Vec<String>,

        /// Output format (auto = table on TTY, JSON when piped)
        #[arg(long, short = 'o', value_enum, default_value_t = OutputFormat::Auto)]
        output: OutputFormat,

        /// Shorthand for --output json
        #[arg(long, conflicts_with = "output")]
        json: bool,

        /// Override domain TLDs to check (repeat flag for multiple)
        #[arg(long, value_name = "TLD")]
        tld: Vec<String>,
    },

    /// Print a machine-readable description of this CLI
    Schema,
}

#[derive(Copy, Clone, Debug, ValueEnum)]
enum OutputFormat {
    Auto,
    Json,
    Table,
    Markdown,
}

impl From<OutputFormat> for Format {
    fn from(v: OutputFormat) -> Self {
        match v {
            OutputFormat::Auto => Format::Auto,
            OutputFormat::Json => Format::Json,
            OutputFormat::Table => Format::Table,
            OutputFormat::Markdown => Format::Markdown,
        }
    }
}

#[tokio::main]
async fn main() {
    let cli = Cli::parse();
    let exit = match cli.command {
        Command::Check {
            names,
            output,
            json,
            tld,
        } => {
            let format = if json { Format::Json } else { output.into() };
            run_check(names, format, tld).await
        }
        Command::Schema => run_schema(),
    };
    std::process::exit(exit);
}

fn run_schema() -> i32 {
    let value = schema::describe();
    match serde_json::to_string_pretty(&value) {
        Ok(s) => {
            println!("{s}");
            0
        }
        Err(e) => {
            CliError::new(ErrorKind::Internal, format!("serialize schema: {e}")).emit();
            ErrorKind::Internal.exit_code()
        }
    }
}

async fn run_check(names: Vec<String>, format: Format, tld_overrides: Vec<String>) -> i32 {
    for name in &names {
        if let Err(err) = validate::validate(name) {
            err.emit();
            return err.kind.exit_code();
        }
    }

    let transport = match ReqwestTransport::new() {
        Ok(t) => t,
        Err(e) => {
            let err = CliError::new(ErrorKind::Internal, format!("transport init failed: {e}"));
            let code = err.kind.exit_code();
            err.emit();
            return code;
        }
    };

    let tlds = if tld_overrides.is_empty() {
        default_tlds()
    } else {
        tld_overrides
    };

    let mut reports = Vec::with_capacity(names.len());
    for name in &names {
        reports.push(check_name(&transport, name, &tlds).await);
    }

    if let Err(err) = output::render(&reports, format) {
        let code = err.kind.exit_code();
        err.emit();
        return code;
    }

    exit_code(&reports)
}

fn exit_code(reports: &[NameReport]) -> i32 {
    let mut any_taken = false;
    let mut any_active = false;
    for report in reports {
        for result in &report.results {
            match &result.status {
                Status::Taken(info) => {
                    any_taken = true;
                    if info.classification == Classification::Active {
                        any_active = true;
                    }
                }
                Status::Unknown { .. } => any_taken = true,
                Status::Free => {}
            }
        }
    }
    match (any_taken, any_active) {
        (false, _) => 0,
        (true, false) => 1,
        (true, true) => 2,
    }
}
