#![allow(dead_code)]
use std::fs;
use std::io::{Read, Write};
use std::os::unix::io::{IntoRawFd, RawFd};
use std::os::unix::net::{UnixListener, UnixStream};
use puffin::agent::{AgentDescriptor, AgentName};
use puffin::algebra::ConcreteMessage;
use puffin::claims::GlobalClaimList;
use puffin::codec::Codec;
use puffin::error::Error;
use puffin::put::{Put, PutOptions};
use puffin::put_registry::Factory;
use puffin::stream::Stream;
use crate::libssh::ssh::{
    SessionOption, SessionState, SshAuthResult, SshBind, SshBindOption, SshKey, SshRequest,
    SshResult, SshSession,
};
use crate::protocol::{AgentType, RawSshMessageFlight, SshDescriptorConfig, SshProtocolBehavior};
use crate::put_registry::LIBSSH_RUST_PUT;
pub mod ssh;
const OPENSSH_RSA_PRIVATE_KEY: &str = "-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
";
pub fn new_libssh_factory() -> Box<dyn Factory<SshProtocolBehavior>> {
    struct LibSSLFactory;
    impl Factory<SshProtocolBehavior> for LibSSLFactory {
        fn create(
            &self,
            agent_descriptor: &AgentDescriptor<SshDescriptorConfig>,
            _claims: &GlobalClaimList<
                <SshProtocolBehavior as puffin::protocol::ProtocolBehavior>::Claim,
            >,
            _options: &PutOptions,
        ) -> Result<Box<dyn Put<SshProtocolBehavior>>, Error> {
            let path = format!("socket_{}", agent_descriptor.name);
            let listener = UnixListener::bind(&path).unwrap();
            listener.set_nonblocking(true).unwrap();
            let fuzz_stream = UnixStream::connect(&path).unwrap();
            fs::remove_file(&path).unwrap();
            fuzz_stream.set_nonblocking(true).unwrap();
            let put_stream = listener.incoming().next().unwrap().unwrap();
            put_stream.set_nonblocking(true).unwrap();
            let mut session = SshSession::new().unwrap();
            session.set_blocking(false);
            session
                .set_options_int(SessionOption::SSH_OPTIONS_PROCESS_CONFIG, 0)
                .unwrap();
            let put_fd = put_stream.into_raw_fd();
            match &agent_descriptor.protocol_config.typ {
                AgentType::Server => {
                    let mut bind = SshBind::new().unwrap();
                    let key = SshKey::from_base64(OPENSSH_RSA_PRIVATE_KEY).unwrap();
                    bind.set_options_key(SshBindOption::SSH_BIND_OPTIONS_IMPORT_KEY, key)
                        .unwrap();
                    bind.set_blocking(false);
                    bind.accept_fd(&session, put_fd).unwrap();
                }
                AgentType::Client => {
                    session
                        .set_options_str(SessionOption::SSH_OPTIONS_HOST, "dummy")
                        .unwrap();
                    session
                        .set_options_int(SessionOption::SSH_OPTIONS_FD, put_fd)
                        .unwrap();
                }
            }
            Ok(Box::new(LibSSL {
                fuzz_stream,
                put_fd,
                agent_descriptor: agent_descriptor.clone(),
                session,
                state: PutState::ExchangingKeys,
            }))
        }
        fn name(&self) -> String {
            String::from(LIBSSH_RUST_PUT)
        }
        fn versions(&self) -> Vec<(String, String)> {
            vec![
                (
                    "harness".to_string(),
                    format!(
                        "{} {}",
                        LIBSSH_RUST_PUT,
                        puffin_build::puffin::full_version()
                    ),
                ),
                (
                    "library".to_string(),
                    format!("libssh ({} / {})", "libssh0104", LibSSL::version()),
                ),
            ]
        }
        fn supports(&self, _capability: &str) -> bool {
            false
        }
        fn clone_factory(&self) -> Box<dyn Factory<SshProtocolBehavior>> {
            Box::new(LibSSLFactory)
        }
    }
    Box::new(LibSSLFactory)
}
#[derive(PartialEq)]
enum PutState {
    ExchangingKeys,
    Authenticating,
    Done,
}
pub struct LibSSL {
    fuzz_stream: UnixStream,
    agent_descriptor: AgentDescriptor<SshDescriptorConfig>,
    session: SshSession,
    state: PutState,
    put_fd: RawFd,
}
impl LibSSL {}
impl Stream<SshProtocolBehavior> for LibSSL {
    fn add_to_inbound(&mut self, message: &ConcreteMessage) {
        self.fuzz_stream.write_all(message).unwrap();
    }
    fn take_message_from_outbound(&mut self) -> Result<Option<RawSshMessageFlight>, Error> {
        let mut buf = vec![];
        let _ = self.fuzz_stream.read_to_end(&mut buf);
        Ok(RawSshMessageFlight::read_bytes(&buf))
    }
}
impl Put<SshProtocolBehavior> for LibSSL {
    fn progress(&mut self) -> Result<(), Error> {
        let session = &mut self.session;
        match &self.agent_descriptor.protocol_config.typ {
            AgentType::Server => match &self.state {
                PutState::ExchangingKeys => match session.handle_key_exchange() {
                    Ok(kex) => {
                        if kex == SshResult::Ok {
                            self.state = PutState::Authenticating;
                        }
                    }
                    Err(err) => {
                        panic!("{}", err)
                    }
                },
                PutState::Authenticating => {
                    if let Some(mut message) = session.get_message() {
                        match message.typ().unwrap() {
                            Some(SshRequest::SSH_REQUEST_AUTH) => {
                                message.auth_reply_success(0).unwrap();
                                self.state = PutState::Done;
                            }
                            _ => {
                                message.reply_default().unwrap();
                            }
                        }
                    }
                }
                PutState::Done => {}
            },
            AgentType::Client => match &self.state {
                PutState::ExchangingKeys => match session.connect() {
                    Ok(kex) => {
                        if kex == SshResult::Ok {
                            self.state = PutState::Authenticating;
                        }
                    }
                    Err(err) => {
                        panic!("{}", err)
                    }
                },
                PutState::Authenticating => match session.userauth_password(None, "test") {
                    Ok(auth) => {
                        if auth == SshAuthResult::Success {
                            self.state = PutState::Done;
                        }
                    }
                    Err(err) => {
                        panic!("{}", err)
                    }
                },
                PutState::Done => {}
            },
        }
        Ok(())
    }
    fn reset(&mut self, _new_name: AgentName) -> Result<(), Error> {
        panic!("Not supported")
    }
    fn descriptor(&self) -> &AgentDescriptor<SshDescriptorConfig> {
        &self.agent_descriptor
    }
    fn describe_state(&self) -> String {
        match self.state {
            PutState::ExchangingKeys => "ExchangingKeys",
            PutState::Authenticating => "Authenticating",
            PutState::Done => "Done",
        }
        .to_owned()
    }
    fn is_state_successful(&self) -> bool {
        self.session.session_state() == SessionState::SSH_SESSION_STATE_AUTHENTICATED
    }
    fn version() -> String
    where
        Self: Sized,
    {
        ssh::version()
    }
    fn shutdown(&mut self) -> String {
        panic!("Not supported")
    }
}