use capnp::capability::Promise;
use opaque_ke::{
    CredentialFinalization, CredentialRequest, RegistrationRequest, RegistrationUpload,
    ServerLogin, ServerRegistration,
};
use quicproquo_core::opaque_auth::OpaqueSuite;
use quicproquo_proto::node_capnp::node_service;

use crate::auth::{coded_error, current_timestamp, PendingLogin, SESSION_TTL_SECS};
use crate::error_codes::*;
use crate::metrics;
use crate::storage::StorageError;

use crate::hooks::AuthEvent;

use super::NodeServiceImpl;

// Audit events in this module must never include secrets (no session tokens, passwords, or raw keys).

fn storage_err(err: StorageError) -> capnp::Error {
    coded_error(E009_STORAGE_ERROR, err)
}

/// Parse username from Cap'n Proto reader; requires valid UTF-8.
fn parse_username_param(
    result: Result<capnp::text::Reader<'_>, capnp::Error>,
) -> Result<String, capnp::Error> {
    let reader = result.map_err(|e| coded_error(E020_BAD_PARAMS, e))?;
    reader
        .to_string()
        .map_err(|_| coded_error(E020_BAD_PARAMS, "username must be valid UTF-8"))
}

impl NodeServiceImpl {
    pub fn handle_opaque_login_start(
        &mut self,
        params: node_service::OpaqueLoginStartParams,
        mut results: node_service::OpaqueLoginStartResults,
    ) -> Promise<(), capnp::Error> {
        let p = match params.get() {
            Ok(p) => p,
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };
        let username = match parse_username_param(p.get_username()) {
            Ok(s) => s,
            Err(e) => return Promise::err(e),
        };
        let request_bytes = match p.get_request() {
            Ok(v) => v.to_vec(),
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };

        if username.is_empty() {
            return Promise::err(coded_error(E011_USERNAME_EMPTY, "username must not be empty"));
        }

        // Check for existing recent pending login before expensive OPAQUE/storage work (DoS mitigation).
        if let Some(existing) = self.pending_logins.get(&username) {
            let age = current_timestamp().saturating_sub(existing.created_at);
            if age < 60 {
                return Promise::err(coded_error(E010_OPAQUE_ERROR, "login already in progress"));
            }
        }

        let credential_request = match CredentialRequest::<OpaqueSuite>::deserialize(&request_bytes) {
            Ok(r) => r,
            Err(e) => {
                return Promise::err(coded_error(
                    E010_OPAQUE_ERROR,
                    format!("invalid credential request: {e}"),
                ))
            }
        };

        let password_file = match self.store.get_user_record(&username) {
            Ok(Some(bytes)) => match ServerRegistration::<OpaqueSuite>::deserialize(&bytes) {
                Ok(pf) => Some(pf),
                Err(e) => {
                    return Promise::err(coded_error(
                        E010_OPAQUE_ERROR,
                        format!("corrupt user record: {e}"),
                    ))
                }
            },
            Ok(None) => None,
            Err(e) => return Promise::err(storage_err(e)),
        };

        let mut rng = rand::rngs::OsRng;
        let result = match ServerLogin::<OpaqueSuite>::start(
            &mut rng,
            &self.opaque_setup,
            password_file,
            credential_request,
            username.as_bytes(),
            Default::default(),
        ) {
            Ok(r) => r,
            Err(e) => {
                return Promise::err(coded_error(
                    E010_OPAQUE_ERROR,
                    format!("OPAQUE login start failed: {e}"),
                ))
            }
        };

        let state_bytes = result.state.serialize().to_vec();
        self.pending_logins.insert(
            username.clone(),
            PendingLogin {
                state_bytes,
                created_at: current_timestamp(),
            },
        );

        let response_bytes = result.message.serialize();
        results.get().set_response(&response_bytes);

        tracing::info!(user = %username, "OPAQUE login started");
        Promise::ok(())
    }

    pub fn handle_opaque_register_start(
        &mut self,
        params: node_service::OpaqueRegisterStartParams,
        mut results: node_service::OpaqueRegisterStartResults,
    ) -> Promise<(), capnp::Error> {
        let p = match params.get() {
            Ok(p) => p,
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };
        let username = match parse_username_param(p.get_username()) {
            Ok(s) => s,
            Err(e) => return Promise::err(e),
        };
        let request_bytes = match p.get_request() {
            Ok(v) => v.to_vec(),
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };

        if username.is_empty() {
            return Promise::err(coded_error(E011_USERNAME_EMPTY, "username must not be empty"));
        }

        if let Ok(true) = self.store.has_user_record(&username) {
            return Promise::err(coded_error(
                E018_USER_EXISTS,
                format!("user '{}' already registered", username),
            ));
        }

        let registration_request = match RegistrationRequest::<OpaqueSuite>::deserialize(&request_bytes) {
            Ok(r) => r,
            Err(e) => {
                return Promise::err(coded_error(
                    E010_OPAQUE_ERROR,
                    format!("invalid registration request: {e}"),
                ))
            }
        };

        let result = match ServerRegistration::<OpaqueSuite>::start(
            &self.opaque_setup,
            registration_request,
            username.as_bytes(),
        ) {
            Ok(r) => r,
            Err(e) => {
                return Promise::err(coded_error(
                    E010_OPAQUE_ERROR,
                    format!("OPAQUE registration start failed: {e}"),
                ))
            }
        };

        let response_bytes = result.message.serialize();
        results.get().set_response(&response_bytes);

        tracing::info!(user = %username, "OPAQUE registration started");
        Promise::ok(())
    }

    pub fn handle_opaque_login_finish(
        &mut self,
        params: node_service::OpaqueLoginFinishParams,
        mut results: node_service::OpaqueLoginFinishResults,
    ) -> Promise<(), capnp::Error> {
        let p = match params.get() {
            Ok(p) => p,
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };
        let username = match parse_username_param(p.get_username()) {
            Ok(s) => s,
            Err(e) => return Promise::err(e),
        };
        let finalization_bytes = match p.get_finalization() {
            Ok(v) => v.to_vec(),
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };
        let identity_key = p.get_identity_key().unwrap_or_default().to_vec();

        if username.is_empty() {
            return Promise::err(coded_error(E011_USERNAME_EMPTY, "username must not be empty"));
        }

        let pending = match self.pending_logins.remove(&username) {
            Some((_, pl)) => pl,
            None => {
                // Audit: login failure — do not log secrets (no token, no password).
                tracing::warn!(user = %username, "audit: auth login failure (no pending login)");
                metrics::record_auth_login_failure_total();
                self.hooks.on_auth(&AuthEvent {
                    username: username.clone(),
                    success: false,
                    failure_reason: "no pending login".to_string(),
                });
                return Promise::err(coded_error(E019_NO_PENDING_LOGIN, "no pending login for this username"))
            }
        };

        let server_login = match ServerLogin::<OpaqueSuite>::deserialize(&pending.state_bytes) {
            Ok(s) => s,
            Err(e) => {
                return Promise::err(coded_error(
                    E010_OPAQUE_ERROR,
                    format!("corrupt login state: {e}"),
                ))
            }
        };

        let finalization = match CredentialFinalization::<OpaqueSuite>::deserialize(&finalization_bytes) {
            Ok(f) => f,
            Err(e) => {
                return Promise::err(coded_error(
                    E010_OPAQUE_ERROR,
                    format!("invalid credential finalization: {e}"),
                ))
            }
        };

        let _result = match server_login.finish(finalization, Default::default()) {
            Ok(r) => r,
            Err(e) => {
                tracing::warn!(user = %username, "audit: auth login failure (OPAQUE finish failed)");
                metrics::record_auth_login_failure_total();
                self.hooks.on_auth(&AuthEvent {
                    username: username.clone(),
                    success: false,
                    failure_reason: format!("OPAQUE finish failed: {e}"),
                });
                return Promise::err(coded_error(
                    E010_OPAQUE_ERROR,
                    format!("OPAQUE login finish failed (bad password?): {e}"),
                ))
            }
        };

        if identity_key.is_empty() {
            metrics::record_auth_login_failure_total();
            return Promise::err(coded_error(
                E016_IDENTITY_MISMATCH,
                "identity key required to bind session token",
            ));
        }

        if let Ok(Some(stored_ik)) = self.store.get_user_identity_key(&username) {
            if stored_ik != identity_key {
                tracing::warn!(user = %username, "audit: auth login failure (identity mismatch)");
                metrics::record_auth_login_failure_total();
                self.hooks.on_auth(&AuthEvent {
                    username: username.clone(),
                    success: false,
                    failure_reason: "identity key mismatch".to_string(),
                });
                return Promise::err(coded_error(
                    E016_IDENTITY_MISMATCH,
                    "identity key does not match registered key",
                ));
            }
        }

        let mut token = [0u8; 32];
        rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut token);
        let token_vec = token.to_vec();

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

        results.get().set_session_token(&token_vec);

        // Hook: on_auth — fires after successful login.
        self.hooks.on_auth(&AuthEvent {
            username: username.clone(),
            success: true,
            failure_reason: String::new(),
        });

        // Audit: login success — do not log session token or any secrets.
        metrics::record_auth_login_success_total();
        tracing::info!(user = %username, "audit: auth login success — session token issued");
        Promise::ok(())
    }

    pub fn handle_opaque_register_finish(
        &mut self,
        params: node_service::OpaqueRegisterFinishParams,
        mut results: node_service::OpaqueRegisterFinishResults,
    ) -> Promise<(), capnp::Error> {
        let p = match params.get() {
            Ok(p) => p,
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };
        let username = match parse_username_param(p.get_username()) {
            Ok(s) => s,
            Err(e) => return Promise::err(e),
        };
        let upload_bytes = match p.get_upload() {
            Ok(v) => v.to_vec(),
            Err(e) => return Promise::err(coded_error(E020_BAD_PARAMS, e)),
        };
        let identity_key = p.get_identity_key().unwrap_or_default().to_vec();

        if username.is_empty() {
            return Promise::err(coded_error(E011_USERNAME_EMPTY, "username must not be empty"));
        }

        match self.store.has_user_record(&username) {
            Ok(true) => {
                return Promise::err(coded_error(
                    E018_USER_EXISTS,
                    format!("user '{}' already registered", username),
                ))
            }
            Err(e) => return Promise::err(storage_err(e)),
            _ => {}
        }

        let upload = match RegistrationUpload::<OpaqueSuite>::deserialize(&upload_bytes) {
            Ok(u) => u,
            Err(e) => {
                return Promise::err(coded_error(
                    E010_OPAQUE_ERROR,
                    format!("invalid registration upload: {e}"),
                ))
            }
        };

        let password_file = ServerRegistration::<OpaqueSuite>::finish(upload);
        let record_bytes = password_file.serialize().to_vec();

        match self
            .store
            .store_user_record(&username, record_bytes)
        {
            Ok(()) => {}
            Err(crate::storage::StorageError::DuplicateUser(_)) => {
                return Promise::err(coded_error(
                    E018_USER_EXISTS,
                    format!("user '{}' already registered", username),
                ))
            }
            Err(e) => return Promise::err(storage_err(e)),
        }

        // Hook: on_user_registered — fires after successful registration.
        self.hooks.on_user_registered(&username, &identity_key);

        if !identity_key.is_empty() {
            if let Err(e) = self
                .store
                .store_user_identity_key(&username, identity_key.clone())
                .map_err(storage_err)
            {
                return Promise::err(e);
            }

            // Append (username, identity_key) to the Key Transparency Merkle log.
            match self.kt_log.lock() {
                Ok(mut log) => {
                    log.append(&username, &identity_key);
                    // Persist after each append (small extra cost, but ensures durability).
                    match log.to_bytes() {
                        Ok(bytes) => {
                            if let Err(e) = self.store.save_kt_log(bytes) {
                                tracing::warn!(user = %username, error = %e, "KT log persist failed");
                            }
                        }
                        Err(e) => {
                            tracing::warn!(user = %username, error = %e, "KT log serialise failed");
                        }
                    }
                    tracing::info!(user = %username, tree_size = log.len(), "KT: appended identity binding");
                }
                Err(e) => {
                    tracing::warn!(user = %username, error = %e, "KT log lock poisoned; skipping append");
                }
            }
        }

        results.get().set_success(true);
        tracing::info!(user = %username, "OPAQUE registration complete");
        Promise::ok(())
    }
}
