use crate::db;
use crate::jwks::{Jwks, ed25519_public_key_to_jwk, x25519_public_key_to_jwk};
use crate::{InviteResponse, MANIFEST_VERSION, ManagedRing, Member, RingspaceError, invite};
use rocket::State;
use rocket::serde::json::Json;
use serde::Deserialize;
use serde_json::{Value, json};
use sqlx::Connection;

#[derive(Deserialize, Debug)]
pub struct InviteRedemption {
    url: String,
    keyset: Jwks,
    name: String,
    description: String,
    manifest_version: i64,
    invite_code: String,
}

#[derive(Responder)]
pub enum InviteError {
    #[response(status = 400, content_type = "json")]
    InviteRedeemed(String),
    #[response(status = 422, content_type = "json")]
    ManifestVersionMismatch(String),
    #[response(status = 500, content_type = "json")]
    EnrollmentFailed(String),
    #[response(status = 401, content_type = "json")]
    InvalidUrl(String),
    #[response(status = 500, content_type = "json")]
    DatabaseError(String),
    #[response(status = 500, content_type = "json")]
    ServerError(String),
}

impl From<sqlx::Error> for InviteError {
    fn from(e: sqlx::Error) -> InviteError {
        InviteError::DatabaseError(e.to_string())
    }
}

impl From<RingspaceError> for InviteError {
    fn from(e: RingspaceError) -> InviteError {
        InviteError::ServerError(e.msg)
    }
}

#[post(
    "/invite/redeem",
    format = "application/json",
    data = "<invite_redemption>"
)]
pub async fn invite_redeem(
    invite_redemption: Json<InviteRedemption>,
    ring: &State<ManagedRing>,
) -> Result<Value, InviteError> {
    let mut conn = db::get_db_connection().await.unwrap();
    println!("{invite_redemption:#?}");
    if let Ok(mut invite) = invite::get_by_code(&mut conn, &invite_redemption.invite_code).await {
        println!("Found invite!");
        println!("{invite:#?}");

        // Check for invite redeemed
        if invite.redeemed {
            conn.close().await.unwrap();
            return Err(InviteError::InviteRedeemed(
                "Invite already redeemed".to_string(),
            ));
        }

        // Check for URL Match
        if invite.url != invite_redemption.url {
            conn.close().await.unwrap();
            return Err(InviteError::InvalidUrl(
                "Invalid URL for invite".to_string(),
            ));
        }

        // Check for manifest version match
        if invite_redemption.manifest_version != MANIFEST_VERSION {
            return Err(InviteError::ManifestVersionMismatch(format!(
                "Manifest version mismatch: server is at {}, site is at {}",
                MANIFEST_VERSION, invite_redemption.manifest_version
            )));
        }

        // Redeem invite
        invite.redeemed = true;
        invite.save(&mut conn).await?;

        // Handle JWK extraction
        let Some(member_signing_key) = invite_redemption.keyset.get_signing_key() else {
            return Err(InviteError::EnrollmentFailed(
                "Invalid or missing signing key".to_string(),
            ));
        };
        let new_member = Member::new(
            invite_redemption.name.clone(),
            invite_redemption.url.clone(),
            invite_redemption.description.clone(),
            invite_redemption.manifest_version,
            member_signing_key.x.clone(),
            invite.ring_id,
        );

        new_member.save(&mut conn).await?;

        let keyset = Jwks {
            keys: vec![
                ed25519_public_key_to_jwk(&ring.public_signing_key)?,
                x25519_public_key_to_jwk(&ring.public_encryption_key)?,
            ],
        };

        let ring = ring.inner();
        let response = InviteResponse {
            url: ring.url.to_owned(),
            name: ring.name.to_owned(),
            description: ring.description.to_owned(),
            manifest_version: ring.manifest_version,
            keyset,
        };
        conn.close().await.unwrap();
        Ok(json!(response))
    } else {
        conn.close().await.unwrap();
        Err(InviteError::EnrollmentFailed(
            "Enrollment failed".to_string(),
        ))
    }
}
