use std::{
    cell::RefCell,
    collections::HashMap,
    fs::File,
    path::{Path, PathBuf},
};

use flate2::read::GzDecoder;
use hex::FromHex;
use sequoia_openpgp::{
    Cert, KeyHandle, KeyID,
    cert::CertBuilder,
    crypto::SessionKey,
    packet::UserID,
    parse::{
        Parse,
        stream::{DecryptionHelper, DecryptorBuilder, MessageStructure, VerificationHelper},
    },
    policy::StandardPolicy,
    types::SymmetricAlgorithm,
};
use tar::Archive;

use crate::{
    crypto::{
        Crypto, CryptoImpl, FindSigningFingerprintStrategy, Fingerprint, Key, VerificationError,
    },
    error::{Error, Result},
    pass::{KeyRingStatus, OwnerTrustLevel, SignatureStatus},
    signature::{Comment, Recipient},
};

pub struct UnpackedDir {
    dir: PathBuf,
}

impl Drop for UnpackedDir {
    fn drop(&mut self) {
        std::fs::remove_dir_all(&self.dir).unwrap();
    }
}

impl UnpackedDir {
    /// Unpacks a `tar.gz` file and returns a reference to the directory.
    ///
    /// # Errors
    /// On io errors and similar.
    pub fn new(name: &str) -> Result<UnpackedDir> {
        let base_path: PathBuf = get_testres_path();

        let packed_file = base_path.join(name.to_owned() + ".tar.gz");

        let tar_gz = File::open(packed_file)?;
        let tar = GzDecoder::new(tar_gz);
        let mut archive = Archive::new(tar);
        archive.unpack(base_path.clone())?;

        Ok(UnpackedDir {
            dir: base_path.join(name),
        })
    }

    #[must_use]
    pub fn path(&self) -> &Path {
        self.dir.as_path()
    }

    #[must_use]
    pub fn dir(&self) -> PathBuf {
        self.dir.clone()
    }
}

fn get_testres_path() -> PathBuf {
    let mut base_path: PathBuf = env!("CARGO_MANIFEST_DIR").into();
    base_path.push("testres");

    base_path
}

#[derive(Clone)]
pub struct MockKey {
    fingerprint: Fingerprint,
    user_id_names: Vec<String>,
}

impl Key for MockKey {
    fn user_id_names(&self) -> Vec<String> {
        self.user_id_names.clone()
    }

    fn fingerprint(&self) -> Result<Fingerprint> {
        Ok(self.fingerprint)
    }

    fn is_not_usable(&self) -> bool {
        false
    }
}

impl Default for MockKey {
    fn default() -> Self {
        Self::new()
    }
}

impl MockKey {
    #[must_use]
    pub fn new() -> MockKey {
        MockKey {
            fingerprint: Fingerprint::V4([
                0x7E, 0x06, 0x80, 0x70, 0xD5, 0xEF, 0x79, 0x4B, 0x00, 0xC8, 0xA9, 0xD9, 0x1D, 0x10,
                0x8E, 0x6C, 0x07, 0xCB, 0xC4, 0x06,
            ]),
            user_id_names: vec!["Alexander Kjäll <alexander.kjall@gmail.com>".to_owned()],
        }
    }

    #[must_use]
    pub fn from_args(fingerprint: Fingerprint, user_id_names: Vec<String>) -> MockKey {
        MockKey {
            fingerprint,
            user_id_names,
        }
    }
}

#[derive(Clone)]
pub struct MockCrypto {
    pub decrypt_called: RefCell<bool>,
    pub encrypt_called: RefCell<bool>,
    pub sign_called: RefCell<bool>,
    pub verify_called: RefCell<bool>,
    encrypt_string_return: Vec<u8>,
    decrypt_string_return: Option<String>,
    sign_string_return: Option<String>,
    encrypt_string_error: Option<String>,
    get_key_string_error: Option<String>,
    get_key_answers: HashMap<String, MockKey>,
}

impl Default for MockCrypto {
    fn default() -> Self {
        Self::new()
    }
}

impl MockCrypto {
    #[must_use]
    pub fn new() -> MockCrypto {
        MockCrypto {
            decrypt_called: RefCell::new(false),
            encrypt_called: RefCell::new(false),
            sign_called: RefCell::new(false),
            verify_called: RefCell::new(false),
            encrypt_string_return: vec![],
            decrypt_string_return: None,
            sign_string_return: None,
            encrypt_string_error: None,
            get_key_string_error: None,
            get_key_answers: HashMap::new(),
        }
    }

    #[must_use]
    pub fn with_encrypt_string_return(mut self, data: Vec<u8>) -> MockCrypto {
        self.encrypt_string_return = data;

        self
    }

    #[must_use]
    pub fn with_decrypt_string_return(mut self, data: &str) -> MockCrypto {
        self.decrypt_string_return = Some(data.to_string());

        self
    }

    #[must_use]
    pub fn with_encrypt_error(mut self, err_str: &str) -> MockCrypto {
        self.encrypt_string_error = Some(err_str.to_string());

        self
    }

    #[must_use]
    pub fn with_get_key_error(mut self, err_str: &str) -> MockCrypto {
        self.get_key_string_error = Some(err_str.to_string());

        self
    }

    #[must_use]
    pub fn with_get_key_result(mut self, key_id: &str, key: MockKey) -> MockCrypto {
        self.get_key_answers.insert(key_id.to_string(), key);

        self
    }

    #[must_use]
    pub fn with_sign_string_return(mut self, sign_str: &str) -> MockCrypto {
        self.sign_string_return = Some(sign_str.to_string());

        self
    }
}

impl Crypto for MockCrypto {
    fn decrypt_string(&self, _: &[u8]) -> Result<String> {
        self.decrypt_called.replace(true);

        match &self.decrypt_string_return {
            Some(s) => Ok(s.clone()),
            None => Ok(String::new()),
        }
    }

    fn encrypt_string(&self, _: &str, _: &[Recipient]) -> Result<Vec<u8>> {
        self.encrypt_called.replace(true);
        if self.encrypt_string_error.is_some() {
            Err(Error::from(self.encrypt_string_error.clone().unwrap()))
        } else {
            Ok(self.encrypt_string_return.clone())
        }
    }

    fn sign_string(
        &self,
        _: &str,
        _: &[Fingerprint],
        _: &FindSigningFingerprintStrategy,
    ) -> Result<String> {
        self.sign_called.replace(true);
        Ok(match self.sign_string_return.as_ref() {
            Some(s) => s.to_owned(),
            None => String::new(),
        })
    }

    fn verify_sign(
        &self,
        _: &[u8],
        _: &[u8],
        _: &[Fingerprint],
    ) -> std::result::Result<SignatureStatus, VerificationError> {
        self.verify_called.replace(true);
        Err(VerificationError::SignatureFromWrongRecipient)
    }

    fn is_key_in_keyring(&self, _recipient: &Recipient) -> Result<bool> {
        Ok(true)
    }

    fn pull_keys(&mut self, _recipients: &[&Recipient], _config_path: &Path) -> Result<String> {
        Ok("dummy implementation".to_owned())
    }

    fn import_key(&mut self, _key: &str, _config_path: &Path) -> Result<String> {
        Ok("dummy implementation".to_owned())
    }

    fn get_key(&self, key_id: &str) -> Result<Box<dyn Key>> {
        if self.get_key_string_error.is_some() {
            Err(Error::from(self.get_key_string_error.clone().unwrap()))
        } else if self.get_key_answers.contains_key(key_id) {
            Ok(Box::new(self.get_key_answers.get(key_id).unwrap().clone()))
        } else {
            Err(Error::from("no key configured"))
        }
    }

    fn get_all_trust_items(&self) -> Result<HashMap<Fingerprint, OwnerTrustLevel>> {
        Ok(HashMap::new())
    }

    fn implementation(&self) -> CryptoImpl {
        CryptoImpl::GpgMe
    }

    fn own_fingerprint(&self) -> Option<Fingerprint> {
        None
    }
}

#[must_use]
pub fn recipient_alex() -> Recipient {
    Recipient {
        name: "Alexander Kjäll <alexander.kjall@gmail.com>".to_owned(),
        comment: Comment {
            pre_comment: None,
            post_comment: None,
        },
        key_id: "1D108E6C07CBC406".to_owned(),
        fingerprint: Some(Fingerprint::V4([
            0x7E, 0x06, 0x80, 0x70, 0xD5, 0xEF, 0x79, 0x4B, 0x00, 0xC8, 0xA9, 0xD9, 0x1D, 0x10,
            0x8E, 0x6C, 0x07, 0xCB, 0xC4, 0x06,
        ])),
        key_ring_status: KeyRingStatus::InKeyRing,
        trust_level: OwnerTrustLevel::Ultimate,
        not_usable: false,
    }
}

#[must_use]
pub fn recipient_alex_old() -> Recipient {
    Recipient {
        name: "Alexander Kjäll <alexander.kjall@gmail.com>".to_owned(),
        comment: Comment {
            pre_comment: None,
            post_comment: None,
        },
        key_id: "DF0C3D316B7312D5".to_owned(),
        fingerprint: Some(Fingerprint::V4([
            0xDB, 0x07, 0xDA, 0xC5, 0xB3, 0x88, 0x2E, 0xAB, 0x65, 0x9E, 0x1D, 0x2F, 0xDF, 0x0C,
            0x3D, 0x31, 0x6B, 0x73, 0x12, 0xD5,
        ])),
        key_ring_status: KeyRingStatus::InKeyRing,
        trust_level: OwnerTrustLevel::Ultimate,
        not_usable: false,
    }
}

/// Generate a Recipient object from a cert
///
/// # Panics
/// If the generation fails.
pub fn recipient_from_cert(cert: &Cert) -> Recipient {
    Recipient {
        name: String::from_utf8(cert.userids().next().unwrap().userid().value().to_vec()).unwrap(),
        comment: Comment {
            pre_comment: None,
            post_comment: None,
        },
        key_id: cert.fingerprint().to_hex(),
        fingerprint: Some(Fingerprint::V4(
            <[u8; 20]>::from_hex(cert.fingerprint().to_hex()).unwrap(),
        )),
        key_ring_status: KeyRingStatus::InKeyRing,
        trust_level: OwnerTrustLevel::Ultimate,
        not_usable: false,
    }
}

#[must_use]
pub fn append_file_name(file: &Path) -> PathBuf {
    let rf = file.to_path_buf();
    let mut sig = rf.into_os_string();
    sig.push(".sig");
    sig.into()
}

/// Generates a pgp cert.
///
/// # Panics
/// If the generation fails.
#[must_use]
pub fn generate_sequoia_cert(email: &str) -> Cert {
    let (cert, _) = CertBuilder::general_purpose([UserID::from(email)])
        .generate()
        .unwrap();

    cert
}

/// Generates a pgp cert.
///
/// # Panics
/// If the generation fails.
#[must_use]
pub fn generate_sequoia_cert_without_private_key(email: &str) -> Cert {
    let (cert, _) = CertBuilder::general_purpose([UserID::from(email)])
        .generate()
        .unwrap();

    cert.strip_secret_key_material()
}

struct KeyLister {
    pub ids: Vec<KeyID>,
}

impl VerificationHelper for &mut KeyLister {
    fn get_certs(&mut self, _: &[KeyHandle]) -> std::result::Result<Vec<Cert>, anyhow::Error> {
        Ok(vec![])
    }

    fn check(&mut self, _structure: MessageStructure) -> std::result::Result<(), anyhow::Error> {
        Ok(())
    }
}

impl DecryptionHelper for &mut KeyLister {
    fn decrypt(
        &mut self,
        pkesks: &[sequoia_openpgp::packet::PKESK],
        _: &[sequoia_openpgp::packet::SKESK],
        _: Option<SymmetricAlgorithm>,
        _: &mut dyn FnMut(Option<SymmetricAlgorithm>, &SessionKey) -> bool,
    ) -> std::result::Result<Option<Cert>, anyhow::Error> {
        self.ids.extend(
            pkesks
                .iter()
                .map(|p| match p.recipient().clone().unwrap() {
                    KeyHandle::Fingerprint(fpr) => Ok(fpr.into()),
                    KeyHandle::KeyID(key_id) => Ok(key_id),
                })
                .collect::<std::result::Result<Vec<KeyID>, anyhow::Error>>()?,
        );
        Ok(None)
    }
}

/// counts the number of recipients of a pgp packet.
///
/// # Panics
/// if a `DecryptorBuilder` can't be built.
#[must_use]
pub fn count_recipients(data: &[u8]) -> usize {
    let p = StandardPolicy::new();
    let mut h = KeyLister { ids: vec![] };

    // result ignored since it's always an error, as we are not decrypting for real
    let _ = DecryptorBuilder::from_bytes(&data)
        .unwrap()
        .with_policy(&p, None, &mut h);

    h.ids.len()
}

#[test]
fn test_count_recipients() {
    let data = vec![
        0xc1, 0x6c, 0x06, 0x15, 0x04, 0x08, 0x6f, 0x6c, 0x69, 0x12, 0x24, 0xad, 0x6e, 0x3c, 0x0c,
        0x86, 0xfc, 0xa2, 0x26, 0xb7, 0x82, 0xd7, 0xfc, 0xd2, 0x44, 0x12, 0x01, 0x07, 0x40, 0x72,
        0xb0, 0x2f, 0x8b, 0x35, 0x5a, 0x34, 0xe1, 0x05, 0xbf, 0x6f, 0x35, 0x2d, 0xc8, 0x33, 0xed,
        0xaa, 0xdf, 0x76, 0xbf, 0xfb, 0x54, 0x8b, 0x73, 0x2c, 0xac, 0x7d, 0xd4, 0xd8, 0xc9, 0xdf,
        0x1b, 0x30, 0x0d, 0x09, 0x53, 0x11, 0x31, 0x03, 0x99, 0xfb, 0x77, 0xa0, 0xa1, 0x1a, 0x0d,
        0x9a, 0xb2, 0xf0, 0x22, 0xe6, 0xf1, 0x63, 0x90, 0x29, 0xb8, 0x37, 0xd4, 0x75, 0xd8, 0x03,
        0xc7, 0x22, 0xdb, 0xe3, 0x9d, 0x62, 0xea, 0x70, 0x69, 0xfa, 0x29, 0x4b, 0x00, 0x11, 0x49,
        0x0c, 0xbf, 0x96, 0x39, 0xa9, 0xd2, 0x54, 0x02, 0x09, 0x02, 0x06, 0x55, 0x14, 0xe8, 0x76,
        0xdd, 0x0f, 0x25, 0x13, 0x16, 0xe5, 0xfd, 0xb4, 0x57, 0x3b, 0xce, 0xa0, 0x3c, 0x81, 0x3d,
        0xc1, 0x82, 0x27, 0x46, 0x91, 0xf1, 0x9e, 0xc1, 0x09, 0x94, 0x9b, 0xbb, 0x55, 0xd4, 0xa4,
        0x26, 0x31, 0xb8, 0x17, 0xef, 0xd8, 0x48, 0xbd, 0x1b, 0x3a, 0xbd, 0x40, 0xec, 0xc6, 0x0b,
        0x33, 0xb0, 0x2f, 0x8c, 0x71, 0xb1, 0x90, 0xf6, 0xda, 0x35, 0xe5, 0x8b, 0xb5, 0x3e, 0x23,
        0xa3, 0x80, 0x35, 0x11, 0x83, 0x79, 0xf4, 0x79, 0x09, 0x71, 0xac, 0xee, 0xc5, 0x65, 0x0e,
        0xb8,
    ];

    assert_eq!(1, count_recipients(&data));
}
