use std::{collections::BTreeMap, str::FromStr};

use async_trait::async_trait;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::io::{DataSource, RecordDeserializer, RecordSerializer};

use super::node::RunnableNode;

#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub enum DeriveColumnType {
    Column(String),
    Constant(String),
}

#[derive(Serialize, Deserialize, Clone, JsonSchema, PartialEq)]
pub enum MatchComparisonType {
    Equal,
    GreaterThan,
    LessThan,
    NotEqual,
    In,
    NotIn,
}

#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub enum DeriveOperation {
    Concat(Vec<DeriveColumnType>),
    Add(Vec<DeriveColumnType>),
    Multiply(Vec<DeriveColumnType>),
    Subtract(Vec<DeriveColumnType>),
    Divide(Vec<DeriveColumnType>),
    Map(String),
}

#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub enum ValueType {
    String,
    Integer,
    Float,
    Boolean,
}

#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub struct DeriveFilter {
    pub column_name: String,
    pub comparator: MatchComparisonType,
    pub match_value: Vec<String>,
    pub value_type: ValueType,
}

pub enum Comparator<T: PartialOrd> {
    Equal(T),
    NotEqual(T),
    GreaterThan(T),
    LessThan(T),
    In(Vec<T>),
    NotIn(Vec<T>),
}

impl<T: PartialOrd> Comparator<T> {
    pub fn is_valid(&self, value: T) -> bool {
        match self {
            Comparator::Equal(v) => value == *v,
            Comparator::NotEqual(v) => value != *v,
            Comparator::GreaterThan(v) => value > *v,
            Comparator::LessThan(v) => value < *v,
            Comparator::In(v) => v.contains(&value),
            Comparator::NotIn(v) => !v.contains(&value),
        }
    }
}

pub trait FieldName {
    // Name of the field this validator should work on
    fn get_field_name(&self) -> String;
}

pub type DataValidators = Vec<Box<dyn DataValidator>>;

pub trait DataValidator: FieldName {
    // Whether the given value is valid for the validator
    fn is_valid(&self, s: &str) -> bool;
}

pub struct FilterRule<T: PartialOrd> {
    pub column_name: String,
    pub comparator: Comparator<T>,
}

impl<T: PartialOrd> FieldName for FilterRule<T> {
    fn get_field_name(&self) -> String {
        self.column_name.clone()
    }
}

impl<T: FromStr + PartialOrd> DataValidator for FilterRule<T> {
    fn is_valid(&self, s: &str) -> bool {
        s.parse().map_or(false, |f| self.comparator.is_valid(f))
    }
}

pub fn to_filter_rules(filters: &Vec<DeriveFilter>) -> anyhow::Result<Vec<Box<dyn DataValidator>>> {
    filters
        .iter()
        .map(|filter| to_filter_rule(filter))
        .collect()
}

fn to_filter_rule(filter: &DeriveFilter) -> anyhow::Result<Box<dyn DataValidator>> {
    let value = &filter.match_value;
    match filter.value_type {
        ValueType::String => Ok(Box::new(get_filter_rule(filter, value.clone()))),
        ValueType::Integer => Ok(Box::new(get_filter_rule(
            filter,
            parse_values(value, |value| value.parse::<i64>())?,
        ))),
        ValueType::Float => Ok(Box::new(get_filter_rule(
            filter,
            parse_values(value, |value| value.parse::<f64>())?,
        ))),
        ValueType::Boolean => Ok(Box::new(get_filter_rule(
            filter,
            parse_values(value, |value| value.parse::<bool>())?,
        ))),
    }
}

fn parse_values<T, E, F>(values: &Vec<String>, parse: F) -> Result<Vec<T>, E>
where
    F: Fn(&String) -> Result<T, E>,
{
    let values: Result<Vec<T>, E> = values.into_iter().map(|value| parse(value)).collect();
    values
}

fn get_filter_rule<T: PartialOrd + Clone>(filter: &DeriveFilter, value: Vec<T>) -> FilterRule<T> {
    FilterRule {
        column_name: filter.column_name.clone(),
        comparator: match filter.comparator {
            MatchComparisonType::Equal => Comparator::Equal(value[0].clone()),
            MatchComparisonType::GreaterThan => Comparator::GreaterThan(value[0].clone()),
            MatchComparisonType::LessThan => Comparator::LessThan(value[0].clone()),
            MatchComparisonType::NotEqual => Comparator::NotEqual(value[0].clone()),
            MatchComparisonType::In => Comparator::In(value),
            MatchComparisonType::NotIn => Comparator::NotIn(value),
        },
    }
}

#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub struct DeriveColumnOperation {
    pub column_name: String,
    pub operation: DeriveOperation,
}

#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub struct DeriveRule {
    pub operations: Vec<DeriveColumnOperation>,
    pub filters: Vec<DeriveFilter>,
    pub copy_all_columns: bool,
}

#[derive(Serialize, Deserialize, Clone, JsonSchema)]
pub struct DeriveNode {
    pub rules: Vec<DeriveRule>,
    pub input_data_source: DataSource,
    pub output_data_source: DataSource,
    pub copy_all_columns: bool,
}

pub struct RunnableDeriveRule {
    pub operations: Vec<DeriveColumnOperation>,
    pub filters: Vec<Box<dyn DataValidator>>,
}

impl DeriveRule {
    fn to_runnable_rule(&self) -> anyhow::Result<RunnableDeriveRule> {
        let filters = to_filter_rules(&self.filters)?;
        Ok(RunnableDeriveRule {
            operations: self.operations.clone(),
            filters,
        })
    }
}

pub fn is_line_valid(line: &BTreeMap<String, String>, rules: &DataValidators) -> bool {
    rules.iter().all(|rule| {
        line.get(&rule.get_field_name()).map_or(true, |value| {
            if value.trim().is_empty() {
                true
            } else {
                rule.is_valid(value)
            }
        })
    })
}

fn concat_columns(line: &BTreeMap<String, String>, columns: &Vec<DeriveColumnType>) -> String {
    columns
        .iter()
        .map(|col| match col {
            DeriveColumnType::Column(column) => line
                .get(column)
                .map(|column| column.clone())
                .unwrap_or("".to_owned()),
            DeriveColumnType::Constant(constant) => constant.clone(),
        })
        .collect()
}

fn reduce_numeric_columns<F>(
    line: &BTreeMap<String, String>,
    columns: &Vec<DeriveColumnType>,
    reducer: F,
) -> String
where
    F: Fn(f64, f64) -> f64,
{
    let value = columns
        .iter()
        .map(|col| match col {
            DeriveColumnType::Column(column) => line
                .get(column)
                .map(|value| value.parse::<f64>().ok())
                .flatten(),
            DeriveColumnType::Constant(constant) => constant.parse().ok(),
        })
        .filter(|value| value.is_some())
        .map(|value| value.unwrap())
        .reduce(reducer);
    value
        .map(|value| value.to_string())
        .unwrap_or("".to_owned())
}

fn derive_line(
    line: BTreeMap<String, String>,
    rules: &Vec<RunnableDeriveRule>,
    output: &mut impl RecordSerializer,
    copy_all_columns: bool,
) -> anyhow::Result<()> {
    let mut output_line;
    if copy_all_columns {
        output_line = line.clone();
    } else {
        output_line = BTreeMap::new();
    }
    for rule in rules {
        if !is_line_valid(&line, &rule.filters) {
            continue;
        }

        for operation in &rule.operations {
            let value = match &operation.operation {
                DeriveOperation::Concat(concat) => concat_columns(&line, concat),
                DeriveOperation::Add(columns) => {
                    reduce_numeric_columns(&line, columns, |a, b| a + b)
                }
                DeriveOperation::Multiply(columns) => {
                    reduce_numeric_columns(&line, columns, |a, b| a * b)
                }
                DeriveOperation::Subtract(columns) => {
                    reduce_numeric_columns(&line, columns, |a, b| a - b)
                }
                DeriveOperation::Divide(columns) => {
                    reduce_numeric_columns(&line, columns, |a, b| a / b)
                }
                DeriveOperation::Map(mapped_value) => mapped_value.clone(),
            };
            output_line.insert(operation.column_name.clone(), value);
        }
    }

    output.serialize(output_line)
}

fn derive(
    rules: &Vec<RunnableDeriveRule>,
    input: &mut impl RecordDeserializer,
    output: &mut impl RecordSerializer,
    copy_all_columns: bool,
) -> anyhow::Result<()> {
    if let Some(line) = input.deserialize()? {
        let line: BTreeMap<String, String> = line;
        output.write_header(&line)?;
        derive_line(line, rules, output, copy_all_columns)?;

        while let Some(line) = input.deserialize()? {
            let line: BTreeMap<String, String> = line;
            derive_line(line, rules, output, copy_all_columns)?;
        }
    }
    Ok(())
}

pub struct DeriveNodeRunner {
    pub derive_node: DeriveNode,
}

#[async_trait]
impl RunnableNode for DeriveNodeRunner {
    async fn run(&self) -> anyhow::Result<()> {
        let mut reader = csv::Reader::from_path(&self.derive_node.input_data_source.path)?;
        let mut writer = csv::Writer::from_path(&self.derive_node.output_data_source.path)?;
        let rules: anyhow::Result<Vec<RunnableDeriveRule>> = self
            .derive_node
            .rules
            .iter()
            .map(|rule| rule.to_runnable_rule())
            .collect();
        let rules = rules?;
        derive(
            &rules,
            &mut reader,
            &mut writer,
            self.derive_node.copy_all_columns,
        )
    }
}
