use clap::{Parser, Subcommand};
use std::io::Write;
use std::io::{BufRead, BufReader};
use std::path::PathBuf;
use std::process::exit;

#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
#[clap(propagate_version = true)]
struct Cli {
    /// file to open, remote or local
    #[clap(name = "FILE")]
    file: PathBuf,

    /// download the file to current directory, similar to run `wget`
    #[clap(short, long)]
    download: bool,

    /// output file path
    #[clap(short, long)]
    outfile: Option<PathBuf>,

    /// cache reading to specified directory
    #[clap(long)]
    cache_dir: Option<String>,

    /// force re-caching if local cache already exists
    #[clap(long)]
    cache_force: bool,

    /// specify cache file name
    #[clap(long)]
    cache_file: Option<String>,

    /// read through file and only print out stats
    #[clap(short, long)]
    stats: bool,

    #[clap(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
enum Commands {
    /// Upload file to S3-compatible object storage
    UploadToS3 {
        /// S3 bucket name
        s3_bucket: String,

        /// S3 file path (starting with `/`)
        s3_path: String,
    },
}

fn main() {
    let cli = Cli::parse();
    let path: &str = cli.file.to_str().unwrap();
    let outfile: Option<PathBuf> = cli.outfile;

    if let Some(command) = cli.command {
        match command {
            Commands::UploadToS3 { s3_bucket, s3_path } => {
                if let Err(e) = oneio::s3_env_check() {
                    eprintln!("missing s3 credentials");
                    eprintln!("{}", e);
                    exit(1);
                }
                match oneio::s3_upload(s3_bucket.as_str(), s3_path.as_str(), path) {
                    Ok(_) => {
                        println!(
                            "file successfully uploaded to s3://{}/{}",
                            s3_bucket, s3_path
                        );
                    }
                    Err(e) => {
                        eprintln!("file upload error: {}", e);
                    }
                }
            }
        }
        return;
    }

    if cli.download {
        let out_path = match outfile {
            None => path.split('/').last().unwrap().to_string(),
            Some(p) => p.to_str().unwrap().to_string(),
        };

        match oneio::download(path, out_path.as_str(), None) {
            Ok(_) => {
                println!("file successfully downloaded to {}", out_path.as_str());
            }
            Err(e) => {
                eprintln!("file download error: {}", e);
            }
        }

        return;
    }

    let reader = Box::new(BufReader::new(match cli.cache_dir {
        Some(dir) => {
            match oneio::get_cache_reader(path, dir.as_str(), cli.cache_file, cli.cache_force) {
                Ok(reader) => reader,
                Err(e) => {
                    eprintln!("Cannot open {}: {}", path, e);
                    return;
                }
            }
        }
        None => match oneio::get_reader(path) {
            Ok(reader) => reader,
            Err(e) => {
                eprintln!("Cannot open {}: {}", path, e);
                return;
            }
        },
    }));

    let mut stdout = std::io::stdout();

    let mut count_lines = 0;
    let mut count_chars = 0;

    for line in reader.lines() {
        let line = match line {
            Ok(l) => l,
            Err(e) => {
                eprintln!("Cannot read line from {}: {}", path, e);
                exit(1);
            }
        };
        if !cli.stats {
            if let Err(e) = writeln!(stdout, "{}", line) {
                if e.kind() != std::io::ErrorKind::BrokenPipe {
                    eprintln!("{}", e);
                    exit(1);
                }
                exit(0);
            }
        }
        count_chars += line.chars().count();
        count_lines += 1;
    }

    if cli.stats {
        println!("lines: \t {}", count_lines);
        println!("chars: \t {}", count_chars);
    }
}
