use std::cell::RefCell;
use std::path::Path;

pub use radicle_ssh as ssh;
pub use ssh::agent::client::{AgentClient, Error};

use crate::{PublicKey, SecretKey, Signature, Signer};

use super::ExtendedSignature;

pub struct Agent {
    client: AgentClient,
}

impl Agent {
    /// Connect to a running SSH agent.
    pub fn connect() -> Result<Self, Error> {
        Ok(Self {
            client: AgentClient::connect_env()?,
        })
    }

    /// Register a key with the agent.
    pub fn register(&mut self, key: &SecretKey) -> Result<(), ssh::Error> {
        self.client.add_identity(key, &[])
    }

    pub fn unregister(&mut self, key: &PublicKey) -> Result<(), ssh::Error> {
        self.client.remove_identity(key)
    }

    pub fn unregister_all(&mut self) -> Result<(), ssh::Error> {
        self.client.remove_all_identities()
    }

    pub fn sign(&mut self, key: &PublicKey, data: &[u8]) -> Result<[u8; 64], ssh::Error> {
        self.client.sign(key, data)
    }

    /// Get a signer from this agent, given the public key.
    pub fn signer(self, key: PublicKey) -> AgentSigner {
        AgentSigner::new(self, key)
    }

    pub fn path(&self) -> Option<&Path> {
        self.client.path()
    }

    pub fn request_identities(&mut self) -> Result<Vec<PublicKey>, ssh::agent::client::Error> {
        self.client.request_identities()
    }
}

/// A [`Signer`] that uses `ssh-agent`.
pub struct AgentSigner {
    agent: RefCell<Agent>,
    public: PublicKey,
}

impl signature::Signer<Signature> for AgentSigner {
    fn try_sign(&self, msg: &[u8]) -> Result<Signature, signature::Error> {
        let sig = self
            .agent
            .borrow_mut()
            .sign(&self.public, msg)
            .map_err(signature::Error::from_source)?;
        Ok(Signature::from(sig))
    }
}

impl signature::Signer<ExtendedSignature> for AgentSigner {
    fn try_sign(&self, msg: &[u8]) -> Result<ExtendedSignature, signature::Error> {
        use signature::Keypair as _;
        Ok(ExtendedSignature {
            key: self.verifying_key(),
            sig: self.try_sign(msg)?,
        })
    }
}

impl AsRef<PublicKey> for AgentSigner {
    fn as_ref(&self) -> &PublicKey {
        &self.public
    }
}

impl signature::KeypairRef for AgentSigner {
    type VerifyingKey = PublicKey;
}

impl AgentSigner {
    pub fn new(agent: Agent, public: PublicKey) -> Self {
        let agent = RefCell::new(agent);

        Self { agent, public }
    }

    pub fn is_ready(&self) -> Result<bool, Error> {
        let ids = self.agent.borrow_mut().request_identities()?;

        Ok(ids.contains(&self.public))
    }

    /// Box this signer into a [`Signer`].
    pub fn boxed(self) -> Box<dyn Signer> {
        Box::new(self)
    }
}
