//! OPAQUE authentication — register and login via the v2 RPC protocol.
//!
//! Wraps the `opaque-ke` crate to perform the OPAQUE 3-message flow against
//! the quicproquo server using prost-encoded protobuf messages over `RpcClient::call`.

use bytes::Bytes;
use opaque_ke::{
    ClientLogin, ClientLoginFinishParameters, ClientRegistration,
    ClientRegistrationFinishParameters, CredentialResponse, RegistrationResponse,
};
use prost::Message;
use quicproquo_core::{opaque_auth::OpaqueSuite, IdentityKeypair};
use quicproquo_proto::{method_ids, qpq::v1};
use quicproquo_rpc::client::RpcClient;

use crate::error::SdkError;

/// Register a new user account via the OPAQUE protocol.
///
/// If `identity` is `None`, a fresh Ed25519 keypair is generated.
/// Returns the identity keypair bound to this account.
pub async fn opaque_register(
    rpc: &RpcClient,
    username: &str,
    password: &str,
    identity: Option<&IdentityKeypair>,
) -> Result<IdentityKeypair, SdkError> {
    let mut rng = rand::rngs::OsRng;

    // Generate or use provided identity.
    let keypair = match identity {
        Some(kp) => IdentityKeypair::from_seed(*kp.seed_bytes()),
        None => IdentityKeypair::generate(),
    };

    // ── Step 1: Registration Start ─────────────────────────────────────────
    let reg_start = ClientRegistration::<OpaqueSuite>::start(&mut rng, password.as_bytes())
        .map_err(|e| SdkError::AuthFailed(format!("OPAQUE register start: {e}")))?;

    let start_req = v1::OpaqueRegisterStartRequest {
        username: username.to_string(),
        request: reg_start.message.serialize().to_vec(),
    };
    let resp_bytes = rpc
        .call(method_ids::OPAQUE_REGISTER_START, Bytes::from(start_req.encode_to_vec()))
        .await?;

    let start_resp = v1::OpaqueRegisterStartResponse::decode(resp_bytes)
        .map_err(|e| SdkError::AuthFailed(format!("decode register_start response: {e}")))?;

    let reg_response = RegistrationResponse::<OpaqueSuite>::deserialize(&start_resp.response)
        .map_err(|e| SdkError::AuthFailed(format!("invalid registration response: {e}")))?;

    // ── Step 2: Registration Finish ────────────────────────────────────────
    let reg_finish = reg_start
        .state
        .finish(
            &mut rng,
            password.as_bytes(),
            reg_response,
            ClientRegistrationFinishParameters::<OpaqueSuite>::default(),
        )
        .map_err(|e| SdkError::AuthFailed(format!("OPAQUE register finish: {e}")))?;

    let finish_req = v1::OpaqueRegisterFinishRequest {
        username: username.to_string(),
        upload: reg_finish.message.serialize().to_vec(),
        identity_key: keypair.public_key_bytes().to_vec(),
    };
    let resp_bytes = rpc
        .call(method_ids::OPAQUE_REGISTER_FINISH, Bytes::from(finish_req.encode_to_vec()))
        .await?;

    let finish_resp = v1::OpaqueRegisterFinishResponse::decode(resp_bytes)
        .map_err(|e| SdkError::AuthFailed(format!("decode register_finish response: {e}")))?;

    if !finish_resp.success {
        return Err(SdkError::AuthFailed("server rejected registration".into()));
    }

    Ok(keypair)
}

/// Log in via the OPAQUE protocol and receive a session token.
///
/// `identity_key` is the 32-byte Ed25519 public key to bind to this session.
/// Returns the session token bytes.
pub async fn opaque_login(
    rpc: &RpcClient,
    username: &str,
    password: &str,
    identity_key: &[u8],
) -> Result<Vec<u8>, SdkError> {
    let mut rng = rand::rngs::OsRng;

    // ── Step 1: Login Start ────────────────────────────────────────────────
    let login_start = ClientLogin::<OpaqueSuite>::start(&mut rng, password.as_bytes())
        .map_err(|e| SdkError::AuthFailed(format!("OPAQUE login start: {e}")))?;

    let start_req = v1::OpaqueLoginStartRequest {
        username: username.to_string(),
        request: login_start.message.serialize().to_vec(),
    };
    let resp_bytes = rpc
        .call(method_ids::OPAQUE_LOGIN_START, Bytes::from(start_req.encode_to_vec()))
        .await?;

    let start_resp = v1::OpaqueLoginStartResponse::decode(resp_bytes)
        .map_err(|e| SdkError::AuthFailed(format!("decode login_start response: {e}")))?;

    let credential_response = CredentialResponse::<OpaqueSuite>::deserialize(&start_resp.response)
        .map_err(|e| SdkError::AuthFailed(format!("invalid credential response: {e}")))?;

    // ── Step 2: Login Finish ───────────────────────────────────────────────
    let login_finish = login_start
        .state
        .finish(
            &mut rng,
            password.as_bytes(),
            credential_response,
            ClientLoginFinishParameters::<OpaqueSuite>::default(),
        )
        .map_err(|e| SdkError::AuthFailed(format!("OPAQUE login finish (bad password?): {e}")))?;

    let finish_req = v1::OpaqueLoginFinishRequest {
        username: username.to_string(),
        finalization: login_finish.message.serialize().to_vec(),
        identity_key: identity_key.to_vec(),
    };
    let resp_bytes = rpc
        .call(method_ids::OPAQUE_LOGIN_FINISH, Bytes::from(finish_req.encode_to_vec()))
        .await?;

    let finish_resp = v1::OpaqueLoginFinishResponse::decode(resp_bytes)
        .map_err(|e| SdkError::AuthFailed(format!("decode login_finish response: {e}")))?;

    if finish_resp.session_token.is_empty() {
        return Err(SdkError::AuthFailed("server returned empty session token".into()));
    }

    Ok(finish_resp.session_token)
}
