extern crate failure;
#[macro_use] extern crate failure_derive;
#[macro_use] extern crate serde_derive;

use reqwest as req;

pub mod mm {
    #[derive(Fail, Debug)]
    pub enum Error {
        #[fail(display = "HTTP error: {}", err)]
        HTTPError { err: reqwest::Error },

        #[fail(display = "Error reading header: {}", err)]
        EncodingError { err: http::header::ToStrError },

        #[fail(display = "Missing header in response: {}", name)]
        HeaderMissingError { name: String },
    }

    impl From<reqwest::Error> for Error {
        fn from(err: reqwest::Error) -> Error {
            Error::HTTPError { err }
        }
    }

    impl From<http::header::ToStrError> for Error {
        fn from(err: http::header::ToStrError) -> Error {
            Error::EncodingError { err }
        }
    }

    pub type Result<T> = core::result::Result<T, Error>;
}

#[derive(Serialize, Deserialize)]
struct Login {
    login_id: String,
    password: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct User {
    id: String,
    create_at: u64,
    update_at: u64,
    delete_at: u64,
    username: String,
    auth_data: String,
    auth_service: String,
    email: String,
    nickname: String,
    first_name: String,
    last_name: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct Team {
    id: String,
    display_name: String,
    name: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct Channel {
    id: String,
    display_name: String,
    name: String,
}

struct Session {
    client: req::Client,
    user: User,
    uri: String,
    token: String,
}

fn temp_password() -> Result<String, failure::Error> {
    Ok(std::str::from_utf8(&std::process::Command::new("pass")
                           .args(&["mattermost/inf"])
                           .output()?.stdout)?.trim().to_string())
}

impl Session {
    fn login(login: &Login, server: &str) -> mm::Result<Session> {
        let uri = format!("{}/api/v4", server);
        let client = req::Client::new();
        let mut res = client.post(&format!("{}/users/login", uri))
            .json(login)
            .send()?;
        let token_val = res.headers().get("token")
            .ok_or(mm::Error::HeaderMissingError { name: "token".to_string() })?;
        let token = format!("Bearer {}", token_val.to_str()?);
        let user = res.json()?;
        Ok(Session {
            user,
            client,
            uri,
            token,
        })
    }

    fn get<T: for<'t> serde::Deserialize<'t>>(&self, path: String) -> mm::Result<T> {
        Ok(self.client.get(&format!("{}/{}", self.uri, path))
            .header("Content-Type", "application/json")
            .header("Authorization", self.token.clone())
            .send()?
            .json()?)
    }

    fn get_me(&self) -> &User {
        &self.user
    }

    fn get_teams(&self) -> mm::Result<Vec<Team>> {
        self.get(format!("users/{}/teams", self.user.id))
    }

    fn get_channels_for_user(&self, user_id: &str, team_id: &str) -> mm::Result<Vec<Channel>> {
        self.get(format!("users/{}/teams/{}/channels", user_id, team_id))
    }
}

fn main() -> mm::Result<()> {
    let password = temp_password().unwrap();
    let login = Login {
        login_id: "gdritter".to_string(),
        password,
    };
    let s = Session::login(&login, "https://mm.infinitenegativeutility.com")?;
    for t in s.get_teams()? {
        let chans = s.get_channels_for_user(&s.get_me().id, &t.id)?;
        println!("Got chans: {:#?}", chans);
    }
    Ok(())
}
