use std::io;
/// This demo application uses a sqlite file to store some data. It does *not* use ORM (that would
/// be done with the `diesel` crate.)!
///
/// A very useful ressource is the
/// [rust-cookbook](https://rust-lang-nursery.github.io/rust-cookbook/database/sqlite.html).
use std::io::{BufRead, Write};

use rusqlite::{Connection, Rows};

mod db;
use db::*;

const USAGE_DELETE: &str = "Usage:
> D cat 15 (to delete cat with id 15)
> D cat 15 16 (to delete cat with id 15 and 16)
> D color 5 (to delete color with id 5)";

fn interactive_add_cat(conn: &Connection) -> anyhow::Result<usize> {
    let stdin = io::stdin();
    print!("the name of your cat?\n> ");
    io::stdout().flush()?;
    let mut cat_name: String = String::new();
    let _ = stdin.read_line(&mut cat_name)?;
    cat_name = cat_name.trim().to_string();

    print!("the color of your cat?\n> ");
    io::stdout().flush()?;
    let mut cat_color: String = String::new();
    let _ = stdin.read_line(&mut cat_color)?;
    cat_color = cat_color.trim().to_string();
    new_color(conn, &cat_color)?;

    let cat_id = new_cat(conn, &cat_name, get_color_id(conn, &cat_color)?)?;
    assert!(check_if_cat_exists(conn, cat_id)?);
    println!("\n-> your cat has id {cat_id}");
    println!("-> your cat has color '{}'", get_cat_color(conn, cat_id)?);

    Ok(cat_id)
}

fn interactive_find_cat(conn: &Connection) -> anyhow::Result<()> {
    let stdin = io::stdin();
    print!("the name of your cat?\n> ");
    io::stdout().flush()?;
    let mut cat_name: String = String::new();
    let _ = stdin.read_line(&mut cat_name)?;
    cat_name = cat_name.trim().to_string();

    print!("the color of your cat?\n> ");
    io::stdout().flush()?;
    let mut cat_color: String = String::new();
    let _ = stdin.read_line(&mut cat_color)?;
    cat_color = cat_color.trim().to_string();

    let mut stmt = conn.prepare(&format!(
        "SELECT * FROM {TABLE_CAT} c, {TABLE_CAT_COLOR} cc
        WHERE c.color_id = cc.id
        AND (c.name LIKE (?1) OR cc.name LIKE (?2))"
    ))?;

    let mut fitting_cats = stmt.query([cat_name, cat_color])?;
    println!("\nThese cats might fit your description:\n");
    print_cats(conn, &mut fitting_cats)?;

    Ok(())
}

fn interactive_delete(conn: &Connection, buf: &mut String) -> anyhow::Result<()> {
    let words: Vec<&str> = buf.split(' ').collect();
    if words.len() < 2 {
        println!("{USAGE_DELETE}");
    } else {
        let stdin = io::stdin();
        let mode = words[1];
        let mut nums: Vec<usize> = Vec::new();
        for word in words[2..].iter() {
            nums.push(match word.parse() {
                Ok(n) => n,
                Err(e) => {
                    eprintln!("Could not parse '{word}' to id: {e}");
                    continue;
                }
            })
        }
        match mode {
            "CAT" => {
                let mut stmt = conn.prepare(&format!("DELETE FROM {TABLE_CAT} WHERE id = (?1)"))?;
                for n in nums {
                    stmt.execute([n])?;
                    println!("deleted cat with id {n}");
                }
            }
            "COLOR" => {
                // Cats have colors, so if we delete a color, we need to delete cats with
                // that color too.
                let mut stmt_how_many_cats_with_color = conn.prepare(&format!(
                    "SELECT COUNT(1) FROM {TABLE_CAT} c, {TABLE_CAT_COLOR}
                            cc WHERE c.color_id = (?1) AND cc.id = (?1)"
                ))?;
                // FIXME: this must still be wrong?
                let mut stmt_cats_with_color = conn.prepare(&format!(
                    "SELECT c.* FROM {TABLE_CAT} c, {TABLE_CAT_COLOR} cc
                        WHERE c.color_id = (?1) AND cc.id = (?1)"
                ))?;
                // works: `SELECT cats.* FROM cats, cat_colors WHERE cats.color_id = 2 AND cat_colors.id = 2;`

                for color_id in &nums {
                    let cats_amount: usize = stmt_how_many_cats_with_color
                        .query_row([color_id], |row| row.get::<_, usize>(0))?;

                    if cats_amount > 0 {
                        let mut cats = stmt_cats_with_color.query([color_id])?; // Get the cats
                                                                                // that would be deleted
                        println!(
                            "\nYou are about to also delete these cats,\n\
                            as they have the color id {color_id}. Type 'YES' to confirm."
                        );
                        print_cats(conn, &mut cats)?;

                        buf.clear();
                        let _ = stdin.lock().read_line(buf); // wait for enter as confirmation
                        *buf = buf.trim().to_string();
                        *buf = buf.to_uppercase().to_string();
                        if buf.as_str() != "YES" {
                            continue;
                        }
                        println!();
                        let mut stmt = conn
                            .prepare(&format!("DELETE FROM {TABLE_CAT} WHERE color_id = (?1)"))?;
                        stmt.execute([color_id])?;
                        println!("-> deleted cats with color_id {color_id}");
                    }

                    let mut stmt =
                        conn.prepare(&format!("DELETE FROM {TABLE_CAT_COLOR} WHERE id = (?1)"))?;
                    stmt.execute([color_id])?;
                    println!("-> deleted color with id {color_id}");
                }
            }
            _ => {
                println!("{USAGE_DELETE}");
            }
        }
    }
    Ok(())
}

fn print_colors(_conn: &Connection, colors: &mut Rows) -> anyhow::Result<()> {
    println!("{: <14}| {: <19}", "id", "name");
    println!("{:=^80}", "");
    while let Some(color) = colors.next()? {
        println!(
            "{:<14}| {: <19}",
            color.get::<_, usize>(0)?,
            color.get::<_, String>(1)?
        )
    }
    Ok(())
}

/// Print [Rows] of cats.
///
/// This needs all columns of the [TABLE_CAT], otherwise it will error.
fn print_cats(conn: &Connection, cats: &mut Rows) -> anyhow::Result<()> {
    println!(
        "{: <14}| {: <19}| {: <19} -> {: <19}",
        "id", "name", "color id", "color name"
    );
    println!("{:=^80}", "");
    while let Some(cat) = cats.next()? {
        println!(
            "{: <14}| {: <19}| {: <19} -> {: <19}",
            cat.get::<_, usize>(0)?,
            cat.get::<_, String>(1)?,
            cat.get::<_, usize>(2)?,
            get_color_by_id(conn, cat.get::<_, usize>(2)?)?.expect("no color for this id"),
        )
    }
    Ok(())
}

fn print_all_data(conn: &Connection) -> anyhow::Result<()> {
    println!("Cat colors:");
    let mut stmt = conn.prepare(&format!("SELECT * FROM {TABLE_CAT_COLOR}"))?;
    let mut colors = stmt.query([])?;
    print_colors(conn, &mut colors)?;

    println!("\n\nCats:");
    let mut stmt = conn.prepare(&format!("SELECT * FROM {TABLE_CAT}"))?;
    let mut cats = stmt.query([])?;
    print_cats(conn, &mut cats)?;

    Ok(())
}

fn main() -> anyhow::Result<()> {
    let conn = connect()?;

    setup(&conn)?;

    let stdin = io::stdin();
    let mut buf: String = String::new();

    loop {
        buf.clear();
        // print!("{}[2J", 27 as char); // clear terminal
        io::stdout().flush()?;
        print!("(A)dd a cat, (F)ind a cat, (P)rint out all data, (D)elete data, or (E)xit?\n> ");
        io::stdout().flush()?;
        let _ = stdin.lock().read_line(&mut buf);
        buf = buf.trim().to_string();
        buf = buf.to_uppercase().to_string();
        if buf.starts_with('A') {
            interactive_add_cat(&conn)?;
        } else if buf.starts_with('F') {
            interactive_find_cat(&conn)?;
        } else if buf.starts_with('P') {
            print_all_data(&conn)?;
        } else if buf.starts_with('D') {
            interactive_delete(&conn, &mut buf)?;
        } else if buf.starts_with('E') {
            println!("Goodbye");
            break;
        }
        println!("\n");
    }

    Ok(())
}