use serde::{Deserialize,Serialize};
use std::{
    io::Write,
    // sync::Arc,
    fmt::Debug,
    fs::{self,File},
    path::{Path, PathBuf},
    io::{Error, ErrorKind, BufRead, BufReader},
    collections::HashMap,
};
use log::error;
use crate::{ 
    defs::{
        FILE_SCHEME,
        SID_TRACE_FILE,
        Config, 
    },
    tools::str_date_from_timestamp,
};

fn default_empty() -> String {
  "/".to_string()
}
fn default_tracedata_items() -> Vec<TraceContent> {
  Vec::new()
}
fn default_tracecontent() -> TraceContent {
  TraceContent::default()
}
fn default_tracecontent_req() -> HashMap<String,String> {
  HashMap::new()
}

#[derive(Default,Deserialize,Serialize,Debug,Clone)]
pub struct RequestData {
    #[serde(default = "default_empty")]
    pub agent: String,
}
#[derive(Default,Deserialize,Serialize,Debug,Clone)]
pub struct TraceContent {
    #[serde(default = "default_empty")]
    pub when: String,
    #[serde(default = "default_empty")]
    pub sid: String,
    #[serde(default = "default_empty")]
    pub origin: String,
    #[serde(default = "default_empty")]
    pub trigger: String,
    #[serde(default = "default_empty")]
    pub id: String,
    #[serde(default = "default_empty")]
    pub info: String,
    #[serde(default = "default_empty")]
    pub context: String,
    #[serde(default = "default_empty")]
    pub role: String,
    #[serde(default = "default_tracecontent_req")]
    pub req: HashMap<String,String>,
}
impl TraceContent {
    pub fn to_json(&self) -> String {
        serde_json::to_string(self).unwrap_or_else(|e|{
            println!("Error to convert TraceContent to json: {}",e);
            String::from("")
        })
    }
}

#[derive(Default,Deserialize,Serialize,Debug,Clone)]
pub struct TraceLogContent {
    #[serde(default = "default_empty")]
    pub line_id: String,
    #[serde(default = "default_tracecontent")]
    pub content: TraceContent,
}

#[derive(Default,Deserialize,Serialize,Debug,Clone)]
pub struct TraceData {
    #[serde(default = "default_empty")]
    pub user_id: String, 
    #[serde(default = "default_empty")]
    pub timestamp: String, 
    #[serde(default = "default_tracedata_items")]
    pub contents: Vec<TraceContent>,
}
impl TraceData {
    fn id_path(&self, file: &str, config: &Config) -> String {
        if self.user_id.is_empty() {
            format!("{}/{}",
                config.trace_store_uri.replace(FILE_SCHEME, ""),
                file)
        } else {
            format!("{}/{}/{}",
                config.trace_store_uri.replace(FILE_SCHEME, ""),
                self.user_id,file)
        }
    }
    fn write_data(&self, file_path: &str, data: &str, overwrite: bool, verbose: u8 ) -> std::io::Result<()> {
        let check_path = |path: &Path| -> std::io::Result<()> {
            if ! Path::new(&path).exists() {
                if let Err(e) = std::fs::create_dir(&path) {
                    return Err(Error::new( ErrorKind::InvalidInput,
                        format!("Error create path {}: {}",&path.display(), e)
                    ));
                }
            }
            Ok(())
        };
        if file_path.is_empty() || data.is_empty() {
            return Err(Error::new(
                ErrorKind::InvalidInput,
                format!("Error save {}",&file_path)
            ));
        }
        if ! Path::new(&file_path).exists() {
            let path = PathBuf::from(&file_path);
            if let Some(dir_path) =  path.parent() {
                if ! Path::new(&dir_path).exists() {
                    if let Some(parent_dir_path) = dir_path.parent() { 
                        if ! Path::new(&parent_dir_path).exists() {
                            let res = check_path(&parent_dir_path);
                            if res.is_err() { return res; }
                        }
                    }
                    let res = check_path(&dir_path);
                    if res.is_err() { return res; }
                }
            }
        }
        if overwrite || ! Path::new(&file_path).exists() {
            fs::write(&file_path, data)?;
            if verbose > 2  { println!("Overwrite: {}",&file_path); }
        } else {
            let sid_settings_file = fs::OpenOptions::new()
                .write(true)
                .append(true) // This is needed to append to file
                .open(&file_path);
            if let Ok(mut file) = sid_settings_file {
                file.write_all(data.as_bytes())?;
            }
            if verbose > 2 { println!("write: {}",&file_path); }
        }
        Ok(())
    }
    pub fn contents_to_json(&self) -> Vec<String> {
        self.contents.clone().into_iter().map(|item| item.to_json()).collect()
    }
    pub fn save(&self, config: &Config, overwrite: bool) -> std::io::Result<()> {
        let file_trace_path = self.id_path(&format!("{}",SID_TRACE_FILE), config);
        let contents = self.contents_to_json();
        let mut result = Ok(());
        let cnt_lines = contents.len();
        let lines = if cnt_lines > 0 {
            cnt_lines - 1
        } else {
            cnt_lines
        };
        let mut write_overwrite = overwrite;
        for (idx, line) in contents.iter().enumerate() {
            let prfx = if idx == 0 && Path::new(&file_trace_path).exists() {
              ",\n"
            } else {
              ""
            };
            let sfx = if idx == lines {
                ""
            } else {
                ",\n"
            };
           result = self.write_data(
                &file_trace_path, 
                format!("{}{}{}",&prfx,&line,&sfx).as_str(),
                write_overwrite,
                config.verbose
            );
            let _ = result.as_ref().unwrap_or_else(|e|{
                println!("Error save trace contets: {} line {}",&e, &idx);
                &()
            });
            if write_overwrite { write_overwrite = false}
            if result.is_err() { break; }
        }
        result
    }
    
    pub fn get_reader(self, config: &Config) -> std::io::Result<BufReader<File>> { 
        let file_path = self.id_path(&format!("{}",SID_TRACE_FILE), config);
        if ! Path::new(&file_path).exists() {
            error!("Error file path not exist: {}",&file_path);
            return Err(Error::new(
                ErrorKind::InvalidInput,
                format!("Error file path not exist: {}",file_path)
            ));
        }
        let file = File::open(&file_path)?;
        let reader = BufReader::new(file);
        Ok(reader)
    }
    pub fn load(&self, config: &Config, human: bool, reverse: bool) -> Vec<TraceLogContent> {
        let mut log: Vec<TraceLogContent> = Vec::new();
        match self.clone().get_reader(config) {
            Ok(reader) => {
                let mut line_pos = 0;
                for line in reader.lines() {
                    line_pos +=1;
                    let line = match line {
                        Ok(res) => if res.ends_with(",") {
                            res[0..res.len() - 1].to_owned()
                        } else {
                            res
                        },
                        Err(_) => continue,
                    };
                    if ! line.is_empty() {
                        let mut log_line: TraceContent = serde_json::from_str(&line).unwrap_or_else(|e| {
                            println!("Error parse load line {}: {}", &line_pos,e);
                            TraceContent::default()
                        });
                        if ! log_line.when.is_empty() { 
                            let line_id = log_line.when.to_owned();
                            if human { 
                                log_line.when =  str_date_from_timestamp(&log_line.when);
                            }
                            log.push(TraceLogContent { line_id, content: log_line }) 
                        }
                    }
                } 
            },
            Err(e) =>  {
                error!("Error on load log: {}",e);
            }
        }
        if reverse { log.reverse(); }
        log
    }
    pub fn clean(&self, config: &Config, line_id: &str) -> std::io::Result<()> {
        let file_path = self.id_path(&format!("{}",SID_TRACE_FILE), config);
        if ! Path::new(&file_path).exists() {
            error!("Error file path not exist: {}",&file_path);
            return Err(Error::new(
                ErrorKind::InvalidInput,
                format!("Error file path not exist: {}",file_path)
            ));
        }
        if  line_id == "ALL" {
            std::fs::remove_file(&file_path)
        } else {
            let mut log: Vec<TraceContent> = Vec::new();
            match self.clone().get_reader(config) {
                Ok(reader) => {
                    let mut line_pos = 0;
                    for line in reader.lines() {
                        line_pos +=1;
                        let line = match line {
                            Ok(res) => if res.ends_with(",") {
                                res[0..res.len() - 1].to_owned()
                            } else {
                                res
                            },
                            Err(_) => continue,
                        };
                        if ! line.is_empty() {
                            let log_line: TraceContent = serde_json::from_str(&line).unwrap_or_else(|e| {
                                println!("Error parse load line {}: {}", &line_pos,e);
                                TraceContent::default()
                            });
                            if ! log_line.when.is_empty() && log_line.when != line_id {
                                log.push(log_line) 
                            }
                        }
                    } 
                },
                Err(e) =>  {
                    error!("Error on load log: {}",e);
                }
            }
            if log.len() == 0 { return std::fs::remove_file(&file_path) }
            let trace_data = TraceData { 
                user_id: self.user_id.to_owned(),
                timestamp: String::from(""),
                contents: log
            };
            trace_data.save(config, true).unwrap_or_else(|e|{
                println!("Error save filter {} to path {}: {}",line_id, &file_path,&e);
                error!("Error save filter {} to path {}: {}",&line_id, &file_path,e);
            });
            Ok(())
        }
    }
}
