use crate::acns::tor::authentication::TorAuthenticationMethod;
use crate::acns::ACNError;
use crate::acns::ACNError::{AuthenticationError, ServiceSetupError};
use ed25519_dalek::ExpandedSecretKey;
use std::io::Write;
use std::io::{BufRead, BufReader, Error};
use std::net::TcpStream;

pub mod authentication;

#[derive(Debug)]
pub struct TorDisconnected(());
#[derive(Debug)]
pub struct TorConnected(());
#[derive(Debug)]
pub struct TorAuthenticated(());

#[derive(Debug)]
pub struct TorProcess<ConnectionStatus> {
    conn: TcpStream,
    status: ConnectionStatus,
}

impl TorProcess<TorDisconnected> {
    pub fn connect(control_port: u16) -> Result<TorProcess<TorConnected>, Error> {
        match TcpStream::connect(&format!("127.0.0.1:{}", control_port)) {
            Ok(conn) => Ok(TorProcess { conn, status: TorConnected(()) }),
            Err(err) => Err(err),
        }
    }
}

/// From the control port spec...
///   Before the client has authenticated, no command other than
///   PROTOCOLINFO, AUTHCHALLENGE, AUTHENTICATE, or QUIT is valid.  If the
///   controller sends any other command, or sends a malformed command, or
///   sends an unsuccessful AUTHENTICATE command, or sends PROTOCOLINFO or
///   AUTHCHALLENGE more than once, Tor sends an error reply and closes
///   the connection.
/// We are more strict as we only allow the caller to authenticate
impl TorProcess<TorConnected> {
    pub fn authenticate(mut self, auth_method: Box<dyn TorAuthenticationMethod>) -> Result<TorProcess<TorAuthenticated>, ACNError> {
        auth_method.authenticate(&mut self.conn);
        let mut reader = BufReader::new(self.conn.try_clone().unwrap());
        let mut result = String::new();
        match reader.read_line(&mut result) {
            Ok(_n) => match result.as_str() {
                "250 OK\r\n" => Ok(TorProcess {
                    conn: self.conn,
                    status: TorAuthenticated(()),
                }),
                "515 Bad authentication\r\n" => Err(AuthenticationError(String::from("515 Bad authentication"))),
                _ => Err(AuthenticationError(String::from(result))),
            },
            Err(err) => Err(AuthenticationError(err.to_string())),
        }
    }
}

/// We are now authenticated!
impl TorProcess<TorAuthenticated> {
    /// Tell the control port to create a new Onion V3 service given the ed25519 secret key...
    pub fn add_onion_v3(&mut self, secret_key: ed25519_dalek::SecretKey, virtual_port: u16, target_port: u16) -> Result<String, ACNError> {
        let esk = ExpandedSecretKey::from(&secret_key);
        match write!(
            self.conn,
            "ADD_ONION ED25519-V3:{} Flags=Detach Port={},{}\r\n",
            base64::encode(esk.to_bytes()),
            virtual_port,
            target_port
        ) {
            _ => {}
        }
        let mut reader = BufReader::new(self.conn.try_clone().unwrap());
        let mut result = String::new();
        match reader.read_line(&mut result) {
            Ok(_n) => {
                if result.starts_with("250-ServiceID=") {
                    // We authenticated!
                    return Ok(result);
                }
                Err(ServiceSetupError(result))
            }
            Err(err) => Err(ServiceSetupError(err.to_string())),
        }
    }
}

#[cfg(test)]
mod tests {
    use crate::acns::tor::authentication::HashedPassword;
    use crate::acns::tor::TorProcess;

    use rand::rngs::OsRng;

    #[test]
    fn connect_tor() {
        let mut auth_control_port = TorProcess::connect(9051)
            .unwrap()
            .authenticate(Box::new(HashedPassword::new(String::from("examplehashedpassword"))))
            .unwrap();
        let mut csprng = OsRng {};
        let keypair = ed25519_dalek::Keypair::generate(&mut csprng);
        match auth_control_port.add_onion_v3(keypair.secret, 9878, 10029) {
            Ok(_) => {
                // we authenticated!
            }
            Err(err) => println!("{:?}", err),
        }
    }
}
