use rand::distributions::{Alphanumeric, DistString};
use std::{
    sync::mpsc::{channel, Receiver, Sender},
    thread::spawn,
};

pub enum Session {
    Ok,
    New(String),
}

struct ValidateSession {
    id: Option<String>,
    tx: Sender<Session>,
}

impl ValidateSession {
    fn new(id: Option<String>, tx: Sender<Session>) -> Self {
        Self { id: id, tx: tx }
    }
}

enum SendMsg {
    ValidateSess(ValidateSession),
}

struct Cache {
    data: Vec<String>,
    rx: Receiver<SendMsg>,
}

impl Cache {
    fn new(recv: Receiver<SendMsg>) -> Self {
        Self {
            rx: recv,
            data: Vec::new(),
        }
    }

    fn gen_id(&self) -> String {
        Alphanumeric.sample_string(&mut rand::thread_rng(), 16)
    }

    fn listen(&mut self) {
        loop {
            match self.rx.recv().unwrap() {
                SendMsg::ValidateSess(vsess) => {
                    vsess.tx.send(self.validate_session(vsess.id)).unwrap()
                }
            }
        }
    }

    fn validate_session(&mut self, sess: Option<String>) -> Session {
        let session: Session;
        if sess.is_some_and(|sess| self.data.contains(&sess)) {
            session = Session::Ok;
        } else {
            let id = self.gen_id();
            self.data.push(id.clone());
            session = Session::New(id);
        }
        session
    }
}

#[derive(Clone)]
pub struct MoreThanText {
    tx: Sender<SendMsg>,
}

impl MoreThanText {
    pub fn new() -> Self {
        let (tx, rx) = channel();
        spawn(move || {
            let mut cache = Cache::new(rx);
            cache.listen();
        });
        Self { tx: tx }
    }

    pub fn get_session(&self, id: Option<String>) -> Session {
        let (tx, rx) = channel();
        self.tx
            .send(SendMsg::ValidateSess(ValidateSession::new(id, tx)))
            .unwrap();
        rx.recv().unwrap()
    }
}

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

    #[test]
    fn session_ids_are_unique() {
        let conn = MoreThanText::new();
        let mut ids: Vec<String> = Vec::new();
        for _ in 1..10 {
            match conn.get_session(None) {
                Session::New(id) => {
                    if ids.contains(&id) {
                        assert!(false, "{} is a duplicate id", id);
                    }
                    ids.push(id)
                }
                Session::Ok => assert!(false, "Should have returned a new id."),
            }
        }
    }

    #[test]
    fn existing_ids_return_ok() {
        let conn = MoreThanText::new();
        let sid: String;
        match conn.get_session(None) {
            Session::New(id) => sid = id,
            Session::Ok => unreachable!(),
        }
        match conn.get_session(Some(sid.clone())) {
            Session::New(_) => assert!(false, "should not create a new session"),
            Session::Ok => {}
        }
    }

    #[test]
    fn bad_ids_get_new_session() {
        let conn = MoreThanText::new();
        let sid = "bad id";
        match conn.get_session(Some(sid.to_string())) {
            Session::New(id) => assert_ne!(sid, id, "do not reuse original id"),
            Session::Ok => assert!(false, "shouuld generate a new id"),
        }
    }
}