use clap::{Parser, Subcommand};
use serde::Deserialize;
use serde_yaml::{
    self,
    Value,
};
use std::{
    collections::HashMap,
    convert::From,
    env::current_dir,
    hash::Hash,
    path::PathBuf,
    fs
};
use thiserror::Error;

#[derive(Parser)]
#[command()]
struct Cli {
    #[command(subcommand)]
    command: Command
}


#[derive(Subcommand)]
enum Command {
    /// Render one or more karbonfiles
    Build {
        /// Path(s) to one or more karbonfiles
        path: Vec<String>,
    }
}


#[derive(Error, Debug)]
enum Error {
    #[error(transparent)]
    StdIo(#[from] std::io::Error),
    #[error("{0} is not a valid karbonfile path or directory")]
    InvalidBuildPath(PathBuf),
    #[error(transparent)]
    SerdeError(#[from] serde_yaml::Error),
}


#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
enum Document {
    Karbonfile {
        apiVersion: String,
        resources: HashMap<String, Option<ResourceOpts>>,
        #[serde(default)]
        transformations: Vec<Transformation>,
    },
    Other(Value),
}


#[derive(Clone, Debug, Deserialize)]
#[serde(untagged)]
enum ResourceOpts {}


#[derive(Clone, Debug)]
struct File {
    path: PathBuf,
    document: Document,
}


#[derive(Clone, Debug)]
struct Variant {
    branch: Vec<File>,
    result: Value,
}

impl Variant {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn render(&mut self) {
        let mut files = self.branch.clone();

        if let Some(File { document: Document::Other(mut doc), ..}) = files.pop() {
            files
                .iter()
                .filter_map(
                    |f| {
                        match f {
                            File { document: Document::Karbonfile { transformations: ts, .. }, .. } => {
                                return Some(ts);
                            },
                            _ => {
                                return None;
                            },
                        }
                    }
                )
                .flatten()
                .for_each(|t| t.apply(&mut doc));

            self.result = doc;
        }
    }
}

impl Default for Variant {
    fn default() -> Self {
        return Self {
            branch: Vec::new(),
            result: Value::Null,
        };
    }
}


#[derive(Clone, Debug, Deserialize)]
#[serde(untagged, rename_all = "lowercase")]
enum Transformation {
    PatchSet {
        #[serde(default)]
        filters: Vec<Filter>,
        patches: HashMap<JsonPointer, JsonPatch>,
    }
}

impl Transformation {
    pub fn apply(&self, doc: &mut Value) {
        match self {
            Transformation::PatchSet {
                filters: fs,
                patches: ps,
            } => {
                if fs.iter().any(|f| f.matches(doc)) {
                    for (ptr, patch) in ps.into_iter() {
                        match patch {
                            JsonPatch::Add(_) => {
                                ptr.new_mut(doc);
                            },
                            JsonPatch::Replace(v) => {
                                *ptr.get_mut(doc) = v.clone();
                            },
                            JsonPatch::Remove => {
                            },
                        }
                    }
                }
            },
        }
    }
}


#[derive(Clone, Debug, Deserialize)]
struct Filter(HashMap<JsonPointer, Value>);

impl Filter {
    pub fn matches(&self, doc: &mut Value) -> bool {
        let Filter(map) = self;

        return map
            .into_iter()
            .all(|(ptr, val)| {
                *ptr.get_mut(doc) == val.clone()
            });
    }
}


#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "lowercase")]
enum JsonPatch {
    Add(Value),
    Replace(Value),
    Remove,
}


#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq)]
#[serde(try_from = "String")]
struct JsonPointer(Vec<String>);

impl JsonPointer {
    pub fn get_mut<'a>(&'a self, doc: &'a mut Value) -> &mut Value {
        let mut result = doc;
        let fields = self.fields();

        Self::walk(&mut result, fields);

        return result;
    }

    pub fn create_mut<'a>(&'a self, doc: &'a mut Value) -> &mut Value {
        let mut result = doc;
        let mut fields = self.fields();

        if let Some(new_field) = fields.pop() {
            Self::walk(&mut result, fields);
        }

        *return = 
        return result;
    }

    fn fields(&self) -> Vec<String> {
        let JsonPointer(fields) = self;

        return fields.clone();
    }

    fn walk(mut result: &mut Value, fields: Vec<String>) -> &mut Value {
       for field in fields {

           if let Ok(number) = field.parse::<usize>() {
                result = result
                   .get_mut(number)
                   .expect("Field {field} wasn't found");
           } else {
                result = result
                   .get_mut(field)
                   .expect("Field {field} wasn't found");
           }

       }

       return result;
    }
}

impl From<String> for JsonPointer {
    fn from(item: String) -> Self {
        if !item.starts_with("/") {
            panic!("{item} is not a valid JSON pointer. It should start with a '/'");
        }
        let result: Vec<String> = item.split('/')
            .skip(1) // Skip the first item (always an empty string)
            .map(|x| x.to_string())
            .collect();
        
        return JsonPointer(result);
    }
}


fn read_file(path: &PathBuf) -> String {
    return fs::read_to_string(path)
        .expect(&format!("Failed to open file {}", path.display()));
}


fn parse(path: &PathBuf) -> Document {
    let content = read_file(path);
    let result: Document = serde_yaml::from_str(&content).unwrap();

    return result;
}


fn normalize(root: &PathBuf, input: &str) -> PathBuf {
    let mut result =  root.clone();

    if result.is_file() {
        result.pop();
    };

    result.push(input);
    return result
        .canonicalize()
        .expect(&format!("Failed to normalize {}", result.display()));
}


fn run(path: PathBuf, result: &mut Vec<Variant>, variant: Variant) {
    let document = parse(&path);
    let file = File {
        path: path.clone(),
        document: document.clone(),
    };
    let mut branch = variant.branch;
    branch.push(file);

    let mut new_variant = Variant {
        branch: branch,
        ..variant
    };

    match document {
        Document::Karbonfile { resources: resources, .. } => {
            // ToDo: Handle karbonfiles without resources (only generators)
            for r in resources.keys() {
                run(normalize(&path, r), result, new_variant.clone());
            };
        },
        Document::Other(_) => {
            new_variant.render();
            result.push(new_variant);
        },
    };
}


fn main() {
    let cli = Cli::parse();
    let root = current_dir().unwrap();

    match cli.command {
        Command::Build { path } => {
            let mut result = Vec::new();

            for p in path {
                let normalized = normalize(&root, &p);
                run(normalized, &mut result, Variant::new());
            };

            println!("{:#?}", result);
        }
    };
}


#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn valid_jsonpointer() {
        assert_eq!(
            JsonPointer::from("/a/b/c".to_string()),
            JsonPointer(vec!["a".to_string(), "b".to_string(), "c".to_string()])
        );
    }
}