use crate::util::surrealdb::DB;

use super::{Member, MembersMigration};

use once_cell::sync::Lazy;
use std::collections::HashMap;
use surrealdb::sql::statements::{BeginStatement, CommitStatement};
use tokio::sync::Mutex;

// Create a store for saving information when migrating to a new members list
static MEMBERS_STORE: Lazy<Mutex<MembersStore>> = Lazy::new(|| Mutex::new(MembersStore::new()));

struct MembersStore {
    store: HashMap<u16, MembersMigration>,
    count: u16,
}

impl MembersStore {
    fn new() -> Self {
        Self {
            store: HashMap::new(),
            count: 0,
        }
    }

    fn insert(&mut self, input: MembersMigration) -> u16 {
        let count = self.count + 1;
        self.store.insert(count, input);
        count
    }

    fn get(&self, key: &u16) -> Option<&MembersMigration> {
        self.store.get(key)
    }

    fn remove(&mut self, key: &u16) -> Option<MembersMigration> {
        self.store.remove(key)
    }
}

// Create a row for the csv file
#[derive(Debug, serde::Deserialize)]
struct Row {
    #[serde(rename = "Relatiecode")]
    id: String,
    #[serde(rename = "Roepnaam")]
    first_name: String,
    #[serde(rename = "Tussenvoegsel(s)")]
    middle_name: String,
    #[serde(rename = "Achternaam")]
    last_name: String,
    #[serde(rename = "E-mail")]
    email: String,
    #[serde(rename = "Verenigingssporten")]
    hours: String,
    #[serde(rename = "Diploma dropdown 1")]
    diploma: Option<String>,
}

impl Row {
    fn to_member(&self) -> Member {
        Member {
            id: self.id.trim().to_string(),
            name: super::Name {
                first: self.first_name.clone(),
                full: self.generate_full_name(),
            },
            hours: self.generate_hours(),
            groups: Vec::new(),
            diploma: self.diploma.clone(),
            registration_token: None,
        }
    }

    // Get the hour data from the raw string
    fn generate_hours(&self) -> Vec<String> {
        let mut hours: Vec<String> = Vec::new();

        let group_parts: Vec<&str> = self.hours.split(", ").collect();

        for group in group_parts {
            let hour_parts: Vec<&str> = group.split(" - ").collect();

            for part in hour_parts {
                if &*part != "Groep" {
                    hours.push(part.to_string());
                }
            }
        }

        hours
    }

    // Generate the full name from 3 parts
    fn generate_full_name(&self) -> String {
        let mut parts: Vec<&str> = vec![];

        parts.push(&self.first_name);
        parts.push(&self.middle_name);
        parts.push(&self.last_name);

        let filtered_strings: Vec<&str> = parts.into_iter().filter(|s| !s.is_empty()).collect();

        filtered_strings.join(" ")
    }
}

impl MembersMigration {
    // Convert the raw csv file to rust objects
    fn csv_to_rows(input: String) -> Result<Vec<Row>, Box<dyn std::error::Error>> {
        let mut members: Vec<Row> = vec![];

        let mut rdr = csv::Reader::from_reader(input.as_bytes());

        for result in rdr.deserialize() {
            let row: Row = result?;
            members.push(row);
        }

        Ok(members)
    }

    // Covert the rows to formatted members
    fn rows_to_members(rows: Vec<Row>) -> Vec<Member> {
        let mut members: Vec<Member> = vec![];

        for row in rows {
            members.push(row.to_member());
        }

        members
    }

    // Compare the new members list with the current
    fn combine_members_lists(
        new_members_list: Vec<Member>,
        current_members_list: Vec<Member>,
    ) -> MembersMigration {
        let current_members_map: HashMap<String, Member> = current_members_list
            .clone()
            .into_iter()
            .map(|m| (m.id.clone(), m))
            .collect();

        let new_members_map: HashMap<String, Member> = new_members_list
            .clone()
            .into_iter()
            .map(|m| (m.id.clone(), m))
            .collect();

        let mut inserted_members: Vec<Member> = vec![];
        let mut updated_members: Vec<Member> = vec![];
        let mut removed_members: Vec<Member> = vec![];

        for current_member in current_members_list {
            if let Some(new_member) = new_members_map.get(&current_member.id) {
                // Update existing member
                let new_member_clone = new_member.clone();

                updated_members.push(Member {
                    id: current_member.id,
                    name: new_member_clone.name,
                    hours: new_member_clone.hours,
                    groups: current_member.groups,
                    diploma: new_member_clone.diploma,
                    registration_token: current_member.registration_token,
                })
            } else {
                // Remove member
                removed_members.push(current_member);
            }
        }

        for new_member in new_members_list {
            // Insert new member
            if !current_members_map.contains_key(&new_member.id) {
                inserted_members.push(new_member);
            }
        }

        Self {
            inserted: inserted_members,
            updated: updated_members,
            removed: removed_members,
        }
    }

    pub async fn migrate_proposal(
        csv: String,
    ) -> Result<(u16, MembersMigration), Box<dyn std::error::Error>> {
        let rows = Self::csv_to_rows(csv)?;

        let new_members = Self::rows_to_members(rows);

        let current_members = Member::fetch_all().await?;

        let members_migration = Self::combine_members_lists(new_members, current_members);

        let count = MEMBERS_STORE.lock().await.insert(members_migration.clone());

        Ok((count, members_migration))
    }

    pub async fn migrate(id: u16) -> Result<(), Box<dyn std::error::Error>> {
        let members_store = MEMBERS_STORE.lock().await;

        let members_migration = match members_store.get(&id) {
            Some(mm) => mm,
            None => return Err("Could not get members from store".into()),
        };

        let mut transaction = DB.query(BeginStatement::default());

        for member in members_migration.updated.clone() {
            // TODO add hours and diploma support
            let id = member.id.clone();

            transaction = transaction.query(format!("UPDATE type::thing('member', $id_{id}) SET name.first = $name_first_{id}, name.full = $name_full_{id}, hours = $hours_{id}, groups = $groups_{id};"))
                .bind((format!("id_{id}"), member.id))
                .bind((format!("name_first_{id}"), member.name.first))
                .bind((format!("name_full_{id}"), member.name.full))
                .bind((format!("hours_{id}"), member.hours))
                .bind((format!("groups_{id}"), member.groups));
        }

        for member in members_migration.inserted.clone() {
            // TODO add hours and diploma support
            let id = member.id.clone();

            transaction = transaction.query(format!("CREATE type::thing('member', $id_{id}) SET name.first = $name_first_{id}, name.full = $name_full_{id}, hours = $hours_{id}, groups = $groups_{id}, registration_token = rand::string(16);"))
                .bind((format!("id_{id}"), member.id))
                .bind((format!("name_first_{id}"), member.name.first))
                .bind((format!("name_full_{id}"), member.name.full))
                .bind((format!("hours_{id}"), member.hours))
                .bind((format!("groups_{id}"), member.groups));
        }

        for member in members_migration.removed.clone() {
            // TODO add hours and diploma support
            let id = member.id.clone();

            transaction = transaction
                .query(format!("DELETE type::thing('member', $id_{id});"))
                .bind((format!("id_{id}"), member.id));
        }

        transaction
            .query(CommitStatement::default())
            .await?
            .check()?;

        Ok(())
    }
}

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

    #[test]
    fn csv_to_members_test() -> Result<(), String> {
        let data = "Relatiecode,Volledige naam (1),Roepnaam,Tussenvoegsel(s),Achternaam,E-mail,2e E-mail,Verenigingssporten,Diploma dropdown 1
            D000001,\"Last, First\",First,,Last,first.last@example.com,first.last@example.nl,\"Groep - Wedstrijd - Zaterdag, Groep - Z5 - Zaterdag\",LS1
            D000002,\"Last2, First2\",First2,,Last2,first1.last@example.nl,,Groep - Z5 - Zaterdag,ZR4".to_string();

        let expected = vec![
            Member {
                id: "D000001".to_string(),
                name: super::super::Name {
                    first: "First".to_string(),
                    full: "First Last".to_string(),
                },
                hours: Vec::from([
                    "Wedstrijd".to_string(),
                    "Z5".to_string(),
                    "Zaterdag".to_string(),
                ]),
                groups: vec![],
                diploma: Some("LS1".to_string()),
                registration_token: None,
            },
            Member {
                id: "D000002".to_string(),
                name: super::super::Name {
                    first: "First2".to_string(),
                    full: "First2 Last2".to_string(),
                },
                hours: Vec::from(["Z5".to_string(), "Zaterdag".to_string()]),
                groups: Vec::new(),
                diploma: Some("ZR4".to_string()),
                registration_token: None,
            },
        ];

        let rows = match MembersMigration::csv_to_rows(data) {
            Ok(r) => r,
            Err(err) => return Err(err.to_string()),
        };

        let members = MembersMigration::rows_to_members(rows);

        assert_eq!(expected, members);

        Ok(())
    }
}
