// Copyright 2021 Heiko Schaefer <heiko@schaefer.name>
//
// SPDX-FileCopyrightText: 2021 Heiko Schaefer <heiko@schaefer.name>
// SPDX-License-Identifier: GPL-3.0-or-later

use anyhow::{Result, anyhow};
use serde::{Deserialize, Serialize};

/// Data structure for an OpenPGP Keylist, as specified in
/// https://www.ietf.org/archive/id/draft-mccain-keylist-05.txt
#[derive(Debug, Serialize, Deserialize)]
pub struct Keylist {
    pub metadata: Metadata,
    pub keys: Vec<Key>,
}

impl Keylist {
    /// Create a Keylist with metadata set, but no Keys
    pub fn new(metadata: Metadata) -> Self {
        Self { metadata, keys: Vec::new() }
    }

    /// Transform this Keylist into a SignedKeylist
    pub fn sign<'a>(
        self,
        sign: Box<dyn Fn(&str) -> Result<String> + 'a>,
    ) -> Result<SignedKeylist> {
        let keylist = serde_json::to_string(&self)?;
        let sig = sign(&keylist)?;

        Ok(SignedKeylist { keylist, sig })
    }
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Metadata {
    // e.g. "https://www.example.com/keylist.json.asc"
    pub signature_uri: String,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub keyserver: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub comment: Option<String>,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct Key {
    pub fingerprint: String,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub keyserver: Option<String>,

    #[serde(skip_serializing_if = "Option::is_none")]
    pub comment: Option<String>,
}

pub struct SignedKeylist {
    pub keylist: String,
    pub sig: String,
}

impl SignedKeylist {
    /// If the SignedKeylist validates (according to `verify_sig`), this
    /// function returns a Keylist (which gives access to the individual
    /// fields of the Keylist).
    ///
    /// `verify_sig` takes a message and a detached signature and checks
    /// if the signature is valid (the certificate that is used to
    /// validate has to be embedded in the Fn - no key store is
    /// used for this test)
    pub fn verify(
        &self,
        verify_sig: Box<dyn FnOnce(&[u8], &str) -> Result<bool>>,
    ) -> Result<Keylist> {
        let verify: Result<bool> =
            verify_sig(self.keylist.as_bytes(), &self.sig);

        if verify.is_ok() && *verify.as_ref().unwrap() {
            Ok(serde_json::from_str(&self.keylist)
                .map_err(|e| anyhow!(e))?)
        } else {
            Err(anyhow!(
                "Signature verification failed {:?}",
                verify
            ))
        }
    }

    /// Retrieve a Keylist from the provided url.
    /// This implicitly also loads the signature_uri from within that Keylist.
    ///
    /// The resulting SignedKeylist needs to be verified as a next step,
    /// before its data can be used.
    pub fn from_url(url: String) -> Result<Self> {
        let keylist = reqwest::blocking::get(&url)?.text()?;

        let kl: Keylist = serde_json::from_str(&keylist)
            .map_err(|e| anyhow!(e))?;

        let sig = reqwest::blocking::get(&kl.metadata.signature_uri)?.text()?;

        Ok(SignedKeylist { keylist, sig })
    }
}
