use crate::db;
use crate::member;
use crate::{
    ManagedRing, MemberManifest, MemberManifestResponse, RingspaceError, RotationRequestPayload,
    UnenrollRequestPayload, verify_value,
};
use rocket::response::status;
use rocket::serde::json::Json;
use rocket::{Responder, State};
use serde::Deserialize;
use serde_json::{Value, json, to_value};
use sqlx::Connection;

#[derive(Responder, Debug)]
pub enum ValidationError {
    #[response(status = 404, content_type = "json")]
    NoMemberFound(String),
    #[response(status = 401, content_type = "json")]
    InvalidManifest(String),
    #[response(status = 406, content_type = "json")]
    InvalidKey(String),
    #[response(status = 500, content_type = "json")]
    ServerError(String),
}

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

impl From<sqlx::Error> for ValidationError {
    fn from(_: sqlx::Error) -> ValidationError {
        ValidationError::ServerError("Database error".to_string())
    }
}

#[derive(Responder, Debug)]
pub enum RotationError {
    #[response(status = 404, content_type = "json")]
    NoMemberFound(String),
    #[response(status = 401, content_type = "json")]
    InvalidKey(String),
    #[response(status = 406, content_type = "json")]
    InvalidSignature(String),
    #[response(status = 500, content_type = "json")]
    RotationFailure(String),
    #[response(status = 500, content_type = "json")]
    VerificationFailure(String),
    #[response(status = 500, content_type = "json")]
    ServerError(String),
}

impl From<sqlx::Error> for RotationError {
    fn from(_: sqlx::Error) -> RotationError {
        RotationError::ServerError("Database error".to_string())
    }
}

impl From<serde_json::Error> for RotationError {
    fn from(_: serde_json::Error) -> RotationError {
        RotationError::ServerError("JSON conversion error".to_string())
    }
}

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

#[derive(Responder, Debug)]
pub enum UnenrollError {
    #[response(status = 404, content_type = "json")]
    NoMemberFound(String),
    #[response(status = 401, content_type = "json")]
    InvalidSignature(String),
    #[response(status = 500, content_type = "json")]
    ServerError(String),
}

impl From<sqlx::Error> for UnenrollError {
    fn from(_: sqlx::Error) -> UnenrollError {
        UnenrollError::ServerError("Database error".to_string())
    }
}

impl From<serde_json::Error> for UnenrollError {
    fn from(_: serde_json::Error) -> UnenrollError {
        UnenrollError::ServerError("JSON conversion error".to_string())
    }
}

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

#[derive(Deserialize, Debug)]
pub struct StandingRequest {
    url: String,
}

#[post(
    "/member/standing",
    format = "application/json",
    data = "<standing_request>"
)]
pub async fn member_standing(
    standing_request: Json<StandingRequest>,
    ring: &State<ManagedRing>,
) -> Result<Value, ValidationError> {
    let mut conn = db::get_db_connection().await?;
    let ring = ring.inner();
    let member = member::get_by_url(&mut conn, &standing_request.url, ring.id).await?;
    let Ok(standing) = member.standing(&mut conn).await else {
        conn.close().await.unwrap();
        return Err(ValidationError::NoMemberFound(format!(
            "Member {} not found",
            standing_request.url
        )));
    };
    conn.close().await.unwrap();
    Ok(json!(standing))
}

#[post(
    "/member/validate",
    format = "application/json",
    data = "<manifest_response>"
)]
pub async fn member_validate(
    manifest_response: Json<MemberManifestResponse>,
    ring: &State<ManagedRing>,
) -> Result<Value, ValidationError> {
    let manifest: MemberManifest = manifest_response.manifest.clone();
    let signature = manifest_response.signature.clone();
    let mut conn = db::get_db_connection().await?;
    let ring = ring.inner();
    // Is it a member?
    let Ok(member) = member::get_by_url(&mut conn, &manifest.url, ring.id).await else {
        conn.close().await.unwrap();
        return Err(ValidationError::NoMemberFound(format!(
            "Member {} not found",
            manifest.url
        )));
    };

    // Extract signing key
    let signing_key = match manifest.keyset.get_signing_key() {
        Some(k) => k,
        None => {
            return Err(ValidationError::InvalidManifest(
                "Missing signing key".to_string(),
            ));
        }
    };

    // Do the keys match?
    if member.public_signing_key != signing_key.x {
        return Err(ValidationError::InvalidKey(
            "Invalid signing key".to_string(),
        ));
    }

    // Convert Manifest to JSON for signature verification
    let manifest_value = to_value(manifest).unwrap();

    // Verify and handle result
    match verify_value(&manifest_value, &signature, &member.public_signing_key) {
        Ok(verification_result) => {
            if verification_result {
                Ok(json!({"result": "success"}))
            } else {
                Err(ValidationError::InvalidManifest(
                    "Validation failed".to_string(),
                ))
            }
        }
        Err(e) => Err(ValidationError::ServerError(e.to_string())),
    }
}

#[post(
    "/member/rotate",
    format = "application/json",
    data = "<rotation_request>"
)]
pub async fn member_rotate(
    rotation_request: Json<RotationRequestPayload>,
    ring: &State<ManagedRing>,
) -> Result<status::Accepted<Value>, RotationError> {
    let mut conn = db::get_db_connection().await?;
    let ring = ring.inner();
    // Check for the member
    let Ok(mut rotation_member) =
        member::get_by_url(&mut conn, &rotation_request.request.member_url, ring.id).await
    else {
        conn.close().await.unwrap();
        return Err(RotationError::NoMemberFound(format!(
            "Member {} not found",
            rotation_request.request.member_url
        )));
    };
    // Does old_key match what we have on record?
    if rotation_member.public_signing_key != rotation_request.request.old_key {
        conn.close().await.unwrap();
        return Err(RotationError::InvalidKey("Invalid old key".to_string()));
    }

    let request_value = to_value(&rotation_request.request)?;

    // Verify the signature
    // This nested match is annoying, but necessary to
    // capture and handle the error from verification
    match verify_value(
        &request_value,
        &rotation_request.signature,
        &rotation_member.public_signing_key,
    ) {
        Ok(verification_result) => {
            match verification_result {
                true => {
                    // Rotate the key and send the result back
                    println!("Request verified");

                    rotation_member.public_signing_key = rotation_request.request.new_key.clone();
                    println!(
                        "Old Key: {}\nNew Key: {}",
                        rotation_request.request.old_key, rotation_member.public_signing_key
                    );
                    match rotation_member.save(&mut conn).await {
                        Ok(_) => Ok(status::Accepted(json!({"result": "success"}))),
                        Err(e) => Err(RotationError::RotationFailure(e.to_string())),
                    }
                }
                false => Err(RotationError::InvalidSignature(
                    "Signature did not verify".to_string(),
                )),
            }
        }
        Err(e) => {
            println!("{e}");
            // Verification failed
            Err(RotationError::VerificationFailure(format!(
                "Verification Failure: {e}"
            )))
        }
    }
}

#[post(
    "/member/unenroll",
    format = "application/json",
    data = "<unenroll_request_payload>"
)]
pub async fn member_unenroll(
    unenroll_request_payload: Json<UnenrollRequestPayload>,
    ring: &State<ManagedRing>,
) -> Result<status::Accepted<Value>, UnenrollError> {
    let mut conn = db::get_db_connection().await?;
    let ring = ring.inner();
    // Get Member or fail
    let Ok(mut member) = member::get_by_url(
        &mut conn,
        &unenroll_request_payload.request.member_url,
        ring.id,
    )
    .await
    else {
        return Err(UnenrollError::NoMemberFound("Member not found".to_string()));
    };

    let request_value = to_value(&unenroll_request_payload.request)?;

    match verify_value(
        &request_value,
        &unenroll_request_payload.signature,
        &member.public_signing_key,
    ) {
        Ok(verification_result) => {
            if verification_result {
                member.enrolled = false;
                member.save(&mut conn).await?;
                return Ok(status::Accepted(json!({"status": "unenrolled"})));
            }
            Err(UnenrollError::ServerError("Could not unenroll".to_string()))
        }
        Err(_) => Err(UnenrollError::InvalidSignature(
            "Invalid signature".to_string(),
        )),
    }
}
