use crate::utils::{generate_encryption_keypair, generate_signing_keypair, sanitize};
use crate::{
    MANIFEST_VERSION, Member, RingManifest, RingManifestResponse, RingspaceError,
    jwks::{Jwks, ed25519_public_key_to_jwk},
    sign_value,
};
use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD as BASE64URL};
use crypto_box::SecretKey;
use serde::Serialize;
use serde_json::{Map, Value, json, to_value};
use sqlx::{Error, FromRow, Sqlite, SqliteConnection, query, query_as, sqlite::SqliteQueryResult};

///
/// The Ring from the Ring server's perspective
///
#[derive(FromRow, Debug, Serialize, Clone)]
pub struct ManagedRing {
    pub id: i64,
    pub url: String,
    pub name: String,
    pub description: String,
    pub manifest_version: i64,
    pub private_signing_key: String,
    pub public_signing_key: String,
    pub private_encryption_key: String,
    pub public_encryption_key: String,
    pub default_ring: bool,
}

impl ManagedRing {
    ///
    /// Creates a new Ring from scratch.
    ///
    /// The `id` field is initialized as `0_i64` to satisfy type requirements, but
    /// will be generated automatically by SQLite upon insertion to the database.
    ///
    pub fn new(name: &str, url: &str, description: &str, default_ring: bool) -> Self {
        let signing_key = generate_signing_keypair();
        let encryption_key = generate_encryption_keypair();

        ManagedRing {
            id: 0,
            name: sanitize(name),
            url: sanitize(url),
            description: sanitize(description),
            manifest_version: MANIFEST_VERSION, // Built-in manifest version determines this
            private_signing_key: signing_key.private_key,
            public_signing_key: signing_key.public_key,
            private_encryption_key: encryption_key.private_key,
            public_encryption_key: encryption_key.public_key,
            default_ring,
        }
    }

    ///
    /// Saves the  managed ring (server-side) to the database
    ///
    pub async fn save(&self, conn: &mut SqliteConnection) -> Result<SqliteQueryResult, Error> {
        // If default, clear existing defaults
        if self.default_ring {
            query(
                "
                UPDATE managed_rings
                SET default_ring = false;
                ",
            )
            .execute(&mut *conn)
            .await?;
        }

        // If the ring doesn't exist (id = 0), add it
        if self.id == 0 {
            return query(
                "
                INSERT INTO managed_rings (name, url, description, manifest_version, private_signing_key, public_signing_key, private_encryption_key, public_encryption_key, default_ring)
                VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)",
            )
            .bind(&self.name)
            .bind(&self.url)
            .bind(&self.description)
            .bind(self.manifest_version)
            .bind(&self.private_signing_key)
            .bind(&self.public_signing_key)
            .bind(&self.private_encryption_key)
            .bind(&self.public_encryption_key)
            .bind(self.default_ring)
            .execute(&mut *conn)
            .await;
        }

        // Otherwise, update
        query(
            "
            UPDATE managed_rings SET
                name = ?,
                url = ?,
                description = ?,
                private_signing_key = ?,
                public_signing_key = ?,
                private_encryption_key = ?,
                public_encryption_key = ?,
                default_ring = ?
            WHERE id = ?",
        )
        .bind(&self.name)
        .bind(&self.url)
        .bind(&self.description)
        .bind(&self.private_signing_key)
        .bind(&self.public_signing_key)
        .bind(&self.private_encryption_key)
        .bind(&self.public_encryption_key)
        .bind(self.default_ring)
        .bind(self.id)
        .execute(&mut *conn)
        .await
    }

    ///
    /// Deletes the ring by ID.
    ///
    pub async fn delete(&self, conn: &mut SqliteConnection) -> Result<SqliteQueryResult, Error> {
        query(
            "
            DELETE FROM managed_rings where id = ?;
            DELETE FROM members where ring_id = ?;
            DELETE FROM member_votes where ring_id = ?;
            ",
        )
        .bind(self.id)
        .bind(self.id)
        .bind(self.id)
        .execute(&mut *conn)
        .await
    }

    ///
    /// Retrieves members
    ///
    pub async fn members(&self, conn: &mut SqliteConnection) -> Result<Vec<Member>, Error> {
        query_as::<Sqlite, Member>(
            "
            SELECT * FROM members
            WHERE ring_id = ? AND enrolled = true
            ",
        )
        .bind(self.id)
        .fetch_all(&mut *conn)
        .await
    }

    ///
    /// Produces the ring manifest in JSON format
    ///
    pub async fn manifest(
        &self,
        conn: &mut SqliteConnection,
    ) -> Result<RingManifestResponse, RingspaceError> {
        // Get members
        let members = self.clone().members(conn).await?;

        // Initialize serde_json struct to hold member data
        let mut members_map = Map::<String, Value>::new();

        for member in members {
            let standing_res = member.standing(conn).await?;
            let sig = ed25519_public_key_to_jwk(&member.public_signing_key)?;
            let mut members_data = json!({
                "name": member.name,
                "url": member.url,
                "description": member.description,
                "keyset": Jwks {keys: vec![sig]},
                "standing": standing_res.standing.to_string()
            });

            // This is critical for signature verification. Keys must be sorted
            // to ensure consistency.
            members_data.sort_all_objects();
            members_map.insert(member.clone().url, members_data);
        }

        // Same here. Sorting for consistency.
        members_map.sort_keys();

        let manifest = RingManifest::new(
            self.name.as_str(),
            self.url.as_str(),
            self.description.as_str(),
            self.public_encryption_key.as_str(),
            self.public_signing_key.as_str(),
            &members_map,
        )?;

        let manifest_value = to_value(&manifest)?;
        let signature = sign_value(&manifest_value, &self.private_signing_key)?.to_bytes();
        let signature = BASE64URL.encode(signature);

        Ok(RingManifestResponse {
            manifest,
            signature,
        })
    }

    ///
    /// Decrypts a single encrypted vote string provided a ring's private encryption key and a
    /// member site's public encryption key.
    ///
    pub async fn decrypt_vote(&self, vote_string: &str) -> Result<String, RingspaceError> {
        let mut private_key_bytes = [0u8; 32];
        BASE64URL
            .decode_slice(
                self.private_encryption_key.as_bytes(),
                &mut private_key_bytes,
            )
            .unwrap();
        let private_key = SecretKey::from(private_key_bytes);
        let ciphertext = BASE64URL.decode(vote_string.as_bytes())?;
        let cleartext = private_key.unseal(&ciphertext)?;

        Ok(String::from_utf8(cleartext)?)
    }
}

///
/// Lists all rings in the database.
///
pub async fn list(conn: &mut SqliteConnection) -> Result<Vec<ManagedRing>, Error> {
    let rings: Vec<ManagedRing> = query_as("SELECT * FROM managed_rings")
        .fetch_all(&mut *conn)
        .await?;
    Ok(rings)
}

///
/// Gets ring by id.
///
pub async fn get_by_id(conn: &mut SqliteConnection, ring_id: i64) -> Result<ManagedRing, Error> {
    let ring: ManagedRing = query_as("SELECT * FROM managed_rings WHERE id = ?")
        .bind(ring_id)
        .fetch_one(&mut *conn)
        .await?;
    Ok(ring)
}

///
/// Gets managed ring by url.
///
pub async fn get_by_url(conn: &mut SqliteConnection, url: &str) -> Result<ManagedRing, Error> {
    let ring: ManagedRing = query_as("SELECT * FROM managed_rings WHERE url = ?")
        .bind(url)
        .fetch_one(&mut *conn)
        .await?;
    Ok(ring)
}

///
/// Retrieves the default managed ring. This is usually
/// The one created with `ring create`, but can be switched with `set_default_ring`.
///
pub async fn get_default(conn: &mut SqliteConnection) -> Result<ManagedRing, Error> {
    query_as::<sqlx::Sqlite, ManagedRing>("SELECT * FROM managed_rings WHERE default_ring = true")
        .fetch_one(&mut *conn)
        .await
}

///
/// Updated Default Ring
///
pub async fn set_default(
    conn: &mut SqliteConnection,
    ring_id: i64,
) -> Result<SqliteQueryResult, Error> {
    query(
        "
        UPDATE managed_rings SET default_ring = false;
        UPDATE managed_rings SET default_ring = true
        WHERE id = ?;
        ",
    )
    .bind(ring_id)
    .execute(&mut *conn)
    .await
}
