use crate::MANIFEST_VERSION;
use crate::RingspaceError;
use crate::jwks;
use crate::jwks::{Jwks, ed25519_public_key_to_jwk};
use crate::site_vote::SiteVote;
use reqwest::{StatusCode, get};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value, from_value};
use time::UtcDateTime;

///
/// Rust Representation of Member Manifests
///
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct MemberManifest {
    pub name: String,
    pub url: String,
    pub description: String,
    pub manifest_type: String,
    pub manifest_version: i64,
    pub keyset: Jwks,
    pub rings: Map<String, Value>,
    pub timestamp: i64,
}

impl MemberManifest {
    pub fn new(
        name: &str,
        url: &str,
        description: &str,
        public_signing_key: &str,
        rings: Map<String, Value>,
    ) -> Result<Self, RingspaceError> {
        let sig_jwk = ed25519_public_key_to_jwk(public_signing_key)?;
        Ok(MemberManifest {
            name: name.to_string(),
            url: url.to_string(),
            description: description.to_string(),
            manifest_type: "member".to_string(),
            manifest_version: MANIFEST_VERSION,
            keyset: Jwks {
                keys: vec![sig_jwk],
            },
            rings,
            timestamp: UtcDateTime::now().unix_timestamp(),
        })
    }
}

///
/// Response wrapper that contains
/// the manifest and signature as separate
/// keys
///
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct MemberManifestResponse {
    pub manifest: MemberManifest,
    pub signature: String,
}

///
/// Rust representation of a Joined Ring Manifest
///
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RingManifest {
    pub name: String,
    pub url: String,
    pub description: String,
    pub manifest_type: String,
    pub keyset: Jwks,
    pub members: Map<String, Value>,
    pub timestamp: i64,
}

impl RingManifest {
    pub fn new(
        name: &str,
        url: &str,
        description: &str,
        public_encryption_key: &str,
        public_signing_key: &str,
        members: &Map<String, Value>,
    ) -> Result<Self, RingspaceError> {
        let sig_jwk = jwks::ed25519_public_key_to_jwk(public_signing_key)?;
        let enc_jwk = jwks::x25519_public_key_to_jwk(public_encryption_key)?;

        Ok(RingManifest {
            name: name.to_string(),
            url: url.to_string(),
            description: description.to_string(),
            manifest_type: "ring".to_string(),
            keyset: Jwks {
                keys: vec![sig_jwk, enc_jwk],
            },
            members: members.to_owned(),
            timestamp: UtcDateTime::now().unix_timestamp(),
        })
    }
}

///
/// The Joined Ring manifest response when requested from the server
///
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct RingManifestResponse {
    pub manifest: RingManifest,
    pub signature: String,
}

impl MemberManifest {
    ///
    /// Returns encrypted votes from the manifest
    ///
    pub async fn votes(self, ring_url: &str) -> Result<Vec<SiteVote>, RingspaceError> {
        if let Some(ring_data) = self.rings.get(ring_url) {
            ring_data
                .get("votes")
                .unwrap()
                .as_object()
                .unwrap()
                .values()
                .map(|v| from_value(v.clone()).unwrap())
                .collect()
        } else {
            Err(RingspaceError {
                msg: "No such ring".to_string(),
            })
        }
    }
}

///
/// Fetches a Member Manifest
///
pub async fn fetch_member(member_url: &str) -> Result<MemberManifestResponse, RingspaceError> {
    let url = format!("{member_url}/.well-known/webrings.json");
    let res = get(url).await?;
    match res.status() {
        StatusCode::OK => match res.json::<MemberManifestResponse>().await {
            Ok(j) => {
                if j.manifest.manifest_version != MANIFEST_VERSION {
                    return Err(RingspaceError::from("Manifest version mismatch"));
                }
                Ok(j)
            }
            Err(e) => Err(RingspaceError {
                msg: format!("{:?}", e),
            }),
        },
        _ => Err(RingspaceError::new(res.text().await.unwrap())),
    }
}

///
/// Fetches a Member Manifest
///
pub async fn fetch_ring(ring_url: &str) -> Result<RingManifestResponse, RingspaceError> {
    let url = format!("{ring_url}/api/manifest");
    let res = get(url).await?;
    match res.status() {
        StatusCode::OK => match res.json().await {
            Ok(j) => Ok(j),
            Err(e) => Err(RingspaceError {
                msg: format!("{:?}", e),
            }),
        },
        _ => Err(RingspaceError::new(res.text().await.unwrap())),
    }
}
