//! OPAQUE auth handlers — registration and login.

use std::sync::Arc;

use bytes::Bytes;
use prost::Message;
use quicproquo_proto::qpq::v1;
use quicproquo_rpc::error::RpcStatus;
use quicproquo_rpc::method::{HandlerResult, RequestContext};

use crate::auth::{PendingLogin, SessionInfo, SESSION_TTL_SECS};
use crate::domain::auth::AuthService;
use crate::domain::types::{RegisterFinishReq, RegisterStartReq};

use super::ServerState;

pub async fn handle_opaque_register_start(
    state: Arc<ServerState>,
    ctx: RequestContext,
) -> HandlerResult {
    let req = match v1::OpaqueRegisterStartRequest::decode(ctx.payload) {
        Ok(r) => r,
        Err(e) => return HandlerResult::err(RpcStatus::BadRequest, &format!("decode: {e}")),
    };

    if req.username.is_empty() {
        return HandlerResult::err(RpcStatus::BadRequest, "username must not be empty");
    }

    let svc = AuthService {
        store: Arc::clone(&state.store),
        opaque_setup: Arc::clone(&state.opaque_setup),
        pending_logins: Arc::clone(&state.pending_logins),
        sessions: Arc::clone(&state.sessions),
        auth_cfg: Arc::clone(&state.auth_cfg),
    };

    let domain_req = RegisterStartReq {
        username: req.username,
        request_bytes: req.request,
    };

    match svc.register_start(domain_req) {
        Ok(resp) => {
            let proto = v1::OpaqueRegisterStartResponse {
                response: resp.response_bytes,
            };
            HandlerResult::ok(Bytes::from(proto.encode_to_vec()))
        }
        Err(e) => HandlerResult::err(RpcStatus::Internal, &format!("register_start: {e}")),
    }
}

pub async fn handle_opaque_register_finish(
    state: Arc<ServerState>,
    ctx: RequestContext,
) -> HandlerResult {
    let req = match v1::OpaqueRegisterFinishRequest::decode(ctx.payload) {
        Ok(r) => r,
        Err(e) => return HandlerResult::err(RpcStatus::BadRequest, &format!("decode: {e}")),
    };

    if req.username.is_empty() {
        return HandlerResult::err(RpcStatus::BadRequest, "username must not be empty");
    }

    let svc = AuthService {
        store: Arc::clone(&state.store),
        opaque_setup: Arc::clone(&state.opaque_setup),
        pending_logins: Arc::clone(&state.pending_logins),
        sessions: Arc::clone(&state.sessions),
        auth_cfg: Arc::clone(&state.auth_cfg),
    };

    let domain_req = RegisterFinishReq {
        username: req.username.clone(),
        upload_bytes: req.upload,
        identity_key: req.identity_key.clone(),
    };

    match svc.register_finish(domain_req) {
        Ok(resp) => {
            state
                .hooks
                .on_user_registered(&req.username, &req.identity_key);

            let proto = v1::OpaqueRegisterFinishResponse {
                success: resp.success,
            };
            HandlerResult::ok(Bytes::from(proto.encode_to_vec()))
        }
        Err(e) => HandlerResult::err(RpcStatus::Internal, &format!("register_finish: {e}")),
    }
}

pub async fn handle_opaque_login_start(
    state: Arc<ServerState>,
    ctx: RequestContext,
) -> HandlerResult {
    let req = match v1::OpaqueLoginStartRequest::decode(ctx.payload) {
        Ok(r) => r,
        Err(e) => return HandlerResult::err(RpcStatus::BadRequest, &format!("decode: {e}")),
    };

    if req.username.is_empty() {
        return HandlerResult::err(RpcStatus::BadRequest, "username must not be empty");
    }

    // Look up user record.
    let user_record = match state.store.get_user_record(&req.username) {
        Ok(Some(r)) => r,
        Ok(None) => {
            return HandlerResult::err(RpcStatus::NotFound, "user not found");
        }
        Err(e) => return HandlerResult::err(RpcStatus::Internal, &format!("store: {e}")),
    };

    // Deserialise stored registration.
    let registration =
        match opaque_ke::ServerRegistration::<quicproquo_core::opaque_auth::OpaqueSuite>::deserialize(&user_record) {
            Ok(r) => r,
            Err(e) => {
                return HandlerResult::err(
                    RpcStatus::Internal,
                    &format!("corrupt user record: {e}"),
                )
            }
        };

    // Start login.
    let credential_request =
        match opaque_ke::CredentialRequest::<quicproquo_core::opaque_auth::OpaqueSuite>::deserialize(&req.request)
        {
            Ok(r) => r,
            Err(e) => {
                return HandlerResult::err(RpcStatus::BadRequest, &format!("bad login request: {e}"))
            }
        };

    let login_start = match opaque_ke::ServerLogin::<
        quicproquo_core::opaque_auth::OpaqueSuite,
    >::start(
        &mut rand::rngs::OsRng,
        &state.opaque_setup,
        Some(registration),
        credential_request,
        req.username.as_bytes(),
        Default::default(),
    ) {
        Ok(r) => r,
        Err(e) => {
            return HandlerResult::err(RpcStatus::Internal, &format!("login start: {e}"));
        }
    };

    let response_bytes = login_start.message.serialize().to_vec();

    // Store pending login state.
    let now = crate::auth::current_timestamp();
    state.pending_logins.insert(
        req.username.clone(),
        PendingLogin {
            state_bytes: login_start.state.serialize().to_vec(),
            created_at: now,
        },
    );

    let proto = v1::OpaqueLoginStartResponse {
        response: response_bytes,
    };
    HandlerResult::ok(Bytes::from(proto.encode_to_vec()))
}

pub async fn handle_opaque_login_finish(
    state: Arc<ServerState>,
    ctx: RequestContext,
) -> HandlerResult {
    let req = match v1::OpaqueLoginFinishRequest::decode(ctx.payload) {
        Ok(r) => r,
        Err(e) => return HandlerResult::err(RpcStatus::BadRequest, &format!("decode: {e}")),
    };

    if req.username.is_empty() {
        return HandlerResult::err(RpcStatus::BadRequest, "username must not be empty");
    }

    // Retrieve pending login state.
    let pending = match state.pending_logins.remove(&req.username) {
        Some((_, p)) => p,
        None => {
            return HandlerResult::err(
                RpcStatus::BadRequest,
                "no pending login for this username",
            );
        }
    };

    let login_state = match opaque_ke::ServerLogin::<
        quicproquo_core::opaque_auth::OpaqueSuite,
    >::deserialize(&pending.state_bytes)
    {
        Ok(s) => s,
        Err(e) => {
            return HandlerResult::err(
                RpcStatus::Internal,
                &format!("corrupt pending login: {e}"),
            )
        }
    };

    let finalization = match opaque_ke::CredentialFinalization::<
        quicproquo_core::opaque_auth::OpaqueSuite,
    >::deserialize(&req.finalization)
    {
        Ok(f) => f,
        Err(e) => {
            return HandlerResult::err(RpcStatus::BadRequest, &format!("bad finalization: {e}"));
        }
    };

    if let Err(e) = login_state.finish(finalization, Default::default()) {
        state.hooks.on_auth(&crate::hooks::AuthEvent {
            username: req.username.clone(),
            success: false,
            failure_reason: format!("{e}"),
        });
        return HandlerResult::err(RpcStatus::Unauthorized, &format!("login failed: {e}"));
    }

    // Generate session token.
    let mut token = vec![0u8; 32];
    rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut token);

    let now = crate::auth::current_timestamp();
    state.sessions.insert(
        token.clone(),
        SessionInfo {
            username: req.username.clone(),
            identity_key: req.identity_key.clone(),
            created_at: now,
            expires_at: now + SESSION_TTL_SECS,
        },
    );

    state.hooks.on_auth(&crate::hooks::AuthEvent {
        username: req.username,
        success: true,
        failure_reason: String::new(),
    });

    let proto = v1::OpaqueLoginFinishResponse {
        session_token: token,
    };
    HandlerResult::ok(Bytes::from(proto.encode_to_vec()))
}
