use std::{
    collections::HashMap,
    fs::File,
    io::{BufReader, BufWriter},
    path::PathBuf,
};

use std::io::Write;

use clap::Parser;

pub use commands::Commands;

use log::info;
use schemars::schema_for;

use crate::{
    create_products::InputFile,
    graph::{Graph, RunnableGraph},
    SourceType,
};

mod commands;

#[derive(Parser)]
#[command(name = "ingey")]
#[command(author = "Pivato M. <contact@michaelpivato.dev>")]
#[command(version = "0.0.1")]
#[command(about = "Simple, fast, efficient data manipulation tool", long_about = None)]
pub struct Cli {
    #[clap(subcommand)]
    pub command: Commands,
}

impl Cli {
    pub async fn run(self) -> anyhow::Result<()> {
        match self.command {
            Commands::AllocateOverheads {
                lines,
                accounts,
                allocation_statistics,
                areas,
                cost_centres,
                use_numeric_accounts,
                account_type,
                exclude_negative_allocation_statistics,
                show_from,
                zero_threshold,
                output,
                msgpack_serialisation,
            } => {
                if msgpack_serialisation {
                    let mut file = BufWriter::new(File::create(output)?);
                    crate::reciprocal_allocation(
                        &mut csv::Reader::from_path(lines)?,
                        &mut csv::Reader::from_path(accounts)?,
                        &mut csv::Reader::from_path(allocation_statistics)?,
                        &mut csv::Reader::from_path(areas)?,
                        &mut csv::Reader::from_path(cost_centres)?,
                        &mut rmp_serde::Serializer::new(&mut file),
                        use_numeric_accounts,
                        exclude_negative_allocation_statistics,
                        true,
                        account_type,
                        show_from,
                        zero_threshold,
                    )
                } else {
                    crate::reciprocal_allocation(
                        &mut csv::Reader::from_path(lines)?,
                        &mut csv::Reader::from_path(accounts)?,
                        &mut csv::Reader::from_path(allocation_statistics)?,
                        &mut csv::Reader::from_path(areas)?,
                        &mut csv::Reader::from_path(cost_centres)?,
                        &mut csv::Writer::from_path(output)?,
                        use_numeric_accounts,
                        exclude_negative_allocation_statistics,
                        true,
                        account_type,
                        show_from,
                        zero_threshold,
                    )
                }
            }
            Commands::CreateProducts {
                definitions,
                encounters,
                services,
                transfers,
                procedures,
                diagnoses,
                patients,
                revenues,
                output,
            } => {
                let mut inputs = HashMap::new();
                inputs.insert(
                    SourceType::Encounter,
                    InputFile {
                        file_path: encounters,
                        joins: HashMap::new(),
                        date_order_column: Some("StartDateTime".to_owned()),
                    },
                );
                inputs.insert(
                    SourceType::Service,
                    InputFile {
                        file_path: services,
                        joins: HashMap::new(),
                        date_order_column: Some("StartDateTime".to_owned()),
                    },
                );
                inputs.insert(
                    SourceType::Transfer,
                    InputFile {
                        file_path: transfers,
                        joins: HashMap::new(),
                        date_order_column: Some("StartDateTime".to_owned()),
                    },
                );
                inputs.insert(
                    SourceType::CodingProcedure,
                    InputFile {
                        file_path: procedures,
                        joins: HashMap::new(),
                        date_order_column: Some("ProcedureDateTime".to_owned()),
                    },
                );
                inputs.insert(
                    SourceType::CodingDiagnosis,
                    InputFile {
                        file_path: diagnoses,
                        joins: HashMap::new(),
                        date_order_column: None,
                    },
                );
                inputs.insert(
                    SourceType::Patient,
                    InputFile {
                        file_path: patients,
                        joins: HashMap::new(),
                        date_order_column: None,
                    },
                );
                inputs.insert(
                    SourceType::Revenue,
                    InputFile {
                        file_path: revenues,
                        joins: HashMap::new(),
                        date_order_column: None,
                    },
                );
                crate::create_products::create_products_polars(definitions, vec![], output)
            }
            Commands::RunGraph { graph, threads } => {
                let file = File::open(graph)?;
                let reader = BufReader::new(file);
                let graph = serde_json::from_reader(reader)?;
                let graph = RunnableGraph::from_graph(graph);

                let result = graph
                    .run_default_tasks(threads, |id, status| {
                        info!("Node with id {} finished with status {:?}", id, status)
                    })
                    .await;
                result
            }
            Commands::GenerateSchema { output } => {
                let schema = schema_for!(Graph);
                let mut output = File::create(output).unwrap();
                write!(output, "{}", serde_json::to_string_pretty(&schema).unwrap())?;
                Ok(())
            }
        }
    }
}
