use crate::MANIFEST_VERSION;
use crate::RingspaceError;
use crate::jwks::Jwks;
use crate::utils::sanitize;
use serde::Serialize;
use sqlx::{Error, FromRow, SqliteConnection, query, query_as, sqlite::SqliteQueryResult};

///
/// The ring from the joining site's perspective
///
#[derive(FromRow, Debug, Serialize)]
pub struct JoinedRing {
    pub id: i64,
    pub url: String,
    pub name: String,
    pub description: String,
    pub manifest_version: i64,
    pub public_signing_key: String,
    pub public_encryption_key: String,
    pub default_ring: bool,
}

impl JoinedRing {
    /// Creates a new Joined ring from incoming Invite Response data.
    /// This provenance is important because this constructor validates
    /// the [Jwks] values are present.
    pub fn new(
        name: &str,
        url: &str,
        description: &str,
        manifest_version: i64,
        keyset: &Jwks,
        default_ring: bool,
    ) -> Result<Self, RingspaceError> {
        // Manifest version sanity check
        // This should never fire, since the server should first reject
        // [InviteRedemption]s that have a version mismatch.
        if manifest_version != MANIFEST_VERSION {
            return Err(RingspaceError::from("Manifest version mismatch"));
        }

        // Confirm key presence
        let Some(signing_jwk) = keyset.get_signing_key() else {
            return Err(RingspaceError::from("Missing signing key"));
        };

        let Some(encryption_jwk) = keyset.get_encryption_key() else {
            return Err(RingspaceError::from("Missing signing key"));
        };

        Ok(JoinedRing {
            id: 0,
            url: sanitize(url),
            name: sanitize(name),
            description: sanitize(description),
            manifest_version,
            public_signing_key: signing_jwk.x.clone(),
            public_encryption_key: encryption_jwk.x.clone(),
            default_ring,
        })
    }

    ///
    /// Creates a joined ring (client-side)
    ///
    pub async fn save(
        &self,
        conn: &mut SqliteConnection,
    ) -> Result<SqliteQueryResult, sqlx::Error> {
        // If default, clear existing defaults
        if self.default_ring {
            query(
                "
                UPDATE joined_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 joined_rings (
                    name,
                    url,
                    description,
                    manifest_version,
                    public_signing_key,
                    public_encryption_key,
                    default_ring
                )
                VALUES(?, ?, ?, ?, ?, ?, ?)",
            )
            .bind(&self.name)
            .bind(&self.url)
            .bind(&self.description)
            .bind(self.manifest_version)
            .bind(&self.public_signing_key)
            .bind(&self.public_encryption_key)
            .bind(self.default_ring)
            .execute(&mut *conn)
            .await;
        }

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

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

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

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

///
/// Retrieves the default joined ring.
///
pub async fn get_default(conn: &mut SqliteConnection) -> Result<JoinedRing, Error> {
    query_as::<sqlx::Sqlite, JoinedRing>("SELECT * FROM joined_rings WHERE default_ring = true")
        .fetch_one(&mut *conn)
        .await
}
