use std::path::Path;
use std::fs;

use serde::{Serialize, Deserialize};
use serde_json::Value;

use color_eyre::eyre::{eyre, Result};

use crate::api::{pk, sp};
use crate::utils::load_json;
use crate::app::App;
use crate::clap_ps::ForceFrom;

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct System {
    pub pk_userid: String,
    pub sp_userid: String,
    pub members: Vec<Member>,
}

#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
pub struct Member {
    pub pk_id: String,
    pub sp_id: String,
    pub name: String,
    pub aliases: Vec<String>
}

impl System {
    pub fn get_system(app: &App, update: bool) -> Result<System>{

        let sys_file_path = format!("{}/system.json", app.cfg_dir);

        if Path::new(&sys_file_path).exists() && !update {
            let json_system: Value = load_json(&sys_file_path)?;
            let sys: System = serde_json::from_value(json_system)?;
            Ok(sys)
        } else {
            match Self::update_system(app) {
                Ok(s) => {
                    Ok(s)
                },
                Err(e) => {
                    Err(e)
                }
            }
        }
    }

    fn update_system(app: &App) -> Result<System> {

        if let Some(conf) = &app.cfg {

            // PluralKit
            let pk_sys = pk::get_system(&conf.pk_key)?;
            let pk_id = pk_sys["id"].as_str().unwrap();
            let pk_members = pk::get_members(&conf.pk_key, &pk_id)?;

            // Simplyplural
            let sp_user_id = sp::get_user_id(&conf.sp_key)?;
            let sp_member_ids = sp::get_member_ids(&conf.sp_key, &sp_user_id)?;

            // Get members
            let mut members: Vec<Member> = Vec::new();
            for pk_mem in pk_members {
                let mut m = Member {
                    pk_id: pk_mem["id"].as_str().unwrap().to_string(),
                    sp_id: String::new(),
                    name: pk_mem["name"].as_str().unwrap().to_string(),
                    aliases: Vec::new(),
                };

                if pk_mem["display_name"].as_str() != None {
                    m.aliases.push(pk_mem["display_name"].as_str().unwrap().to_string());
                }

                m.sp_id = sp::get_member_id(&m, &sp_member_ids)?;

                members.push(m);
            }

            // Create system
            let sys = System {
                pk_userid: pk_id.to_string(),
                sp_userid: sp_user_id,
                members: members.clone(),
            };

            let json = serde_json::to_string(&sys)?;
            fs::write(format!("{}/system.json", &app.cfg_dir), &json)?;

            return Ok(sys);
        }

        Err(eyre!("No configuration loaded!"))
    }
}

#[derive(Debug, Clone)]
pub struct Fronters {
    pub sp: Vec<Member>,
    pub pk: Vec<Member>,
}

impl Fronters {
    pub fn new(pk: Vec<Member>, sp: Vec<Member>) -> Self {
        Self {sp, pk}
    }

    pub fn get(app: &App, ff: ForceFrom) -> Result<Self> {
        if let Some(cfg) = &app.cfg {
            if let Some(sys) = &app.sys {
                let mut fronters = Fronters::new(
                    pk::get_fronters(&cfg.pk_key, &sys)?,
                    sp::get_fronters(&cfg.sp_key, &sys)?,
                );

                if fronters.pk != fronters.sp {
                    match ff {
                        ForceFrom::SP => {
                            fronters.pk = fronters.sp.clone();
                        }
                        ForceFrom::PK => {
                            fronters.sp = fronters.pk.clone();
                        }
                        ForceFrom::None => {

                        }
                    }
                }

                return Ok(fronters);
            }
        }
        Err(eyre!("System or configuration missing"))
    }

    pub fn set(app: &App, to_front: Vec<Member>) -> Result<Fronters> {

        if let Some(cfg) = &app.cfg {
            if let Some(sys) = &app.sys {
                if let Some(fronters) = &app.fronters {
                    pk::set_fronters(&cfg.pk_key, &sys, &to_front, &fronters)?;
                    sp::set_fronters(&cfg.sp_key, &to_front, &fronters)?;

                    return Ok(Fronters::new(to_front.clone(), to_front.clone()));
                }
                return Err(eyre!("Missing Fronters"));
            }
            return Err(eyre!("Missing System"));
        }
        Err(eyre!("Missing Config"))
    }
}