use async_trait::async_trait;
use chrono::{TimeZone, Utc};

use athena_auth_core::adapters::DatabaseAdapter;
use athena_auth_core::entity::{AuthPasskey, AuthSession, AuthUser, AuthVerification};
use athena_auth_core::{AuthContext, AuthError, AuthResult, CreatePasskey, CreateVerification};

use passkey_server::types::{
    CredentialDescriptor, LoginResponse as WebauthnLoginResponse, PasskeyConfig as ServerConfig,
    PasskeyState, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions,
    RegistrationResponse as WebauthnRegistrationResponse, StoredPasskey,
};
use passkey_server::{
    PasskeyError, PasskeyStore, finish_login, finish_registration, start_login, start_registration,
};

use crate::plugins::StatusResponse;
use crate::plugins::helpers::{session_with_server_version, write_sign_in_audit_in_background};

use super::PasskeyConfig;
use super::types::{
    DeletePasskeyRequest, PasskeyResponse, PasskeyView, SessionUserResponse, UpdatePasskeyRequest,
    VerifyAuthenticationRequest, VerifyRegistrationRequest,
};

const PASSKEY_STATE_PREFIX: &str = "passkey_state";

fn now_ms() -> i64 {
    Utc::now().timestamp_millis()
}

fn to_server_config(config: &PasskeyConfig) -> ServerConfig {
    ServerConfig {
        rp_id: config.rp_id.clone(),
        rp_name: config.rp_name.clone(),
        origin: config.origin.clone(),
        state_ttl: config.challenge_ttl_secs,
    }
}

fn to_auth_error(err: PasskeyError) -> AuthError {
    match err {
        PasskeyError::DatabaseError(message) => {
            AuthError::internal(format!("Passkey storage error: {message}"))
        }
        PasskeyError::RegistrationSessionExpired | PasskeyError::LoginSessionExpired => {
            AuthError::bad_request(err.to_string())
        }
        PasskeyError::InvalidChallenge
        | PasskeyError::OriginMismatch { .. }
        | PasskeyError::InvalidOperationType
        | PasskeyError::RpIdHashMismatch
        | PasskeyError::UserPresentFlagNotSet
        | PasskeyError::InvalidSignature(_)
        | PasskeyError::UserHandleMismatch
        | PasskeyError::SignatureCounterRegression
        | PasskeyError::SerializationError(_)
        | PasskeyError::Base64Error(_)
        | PasskeyError::InternalError(_) => AuthError::bad_request(err.to_string()),
        PasskeyError::PasskeyNotFound => AuthError::bad_request("Passkey not found for credential"),
    }
}

fn parse_response_payload(raw: &serde_json::Value, field: &str) -> AuthResult<serde_json::Value> {
    match raw {
        serde_json::Value::Object(_) => Ok(raw.clone()),
        serde_json::Value::String(json) => serde_json::from_str::<serde_json::Value>(json)
            .map_err(|error| AuthError::bad_request(format!("Invalid {field} JSON: {error}"))),
        _ => Err(AuthError::bad_request(format!(
            "Invalid {field}: expected object or JSON string"
        ))),
    }
}

fn normalize_webauthn_response_payload(
    raw: &serde_json::Value,
    field: &str,
) -> AuthResult<serde_json::Value> {
    let mut payload = parse_response_payload(raw, field)?;

    for _ in 0..3 {
        let Some(object) = payload.as_object() else {
            return Ok(payload);
        };

        if object.contains_key("id") {
            return Ok(payload);
        }

        let Some(next) = object.get("response") else {
            return Ok(payload);
        };

        payload = parse_response_payload(next, field)?;
    }

    Ok(payload)
}

fn parse_registration_response(
    body: &VerifyRegistrationRequest,
) -> AuthResult<WebauthnRegistrationResponse> {
    let payload = normalize_webauthn_response_payload(&body.response, "registration response")?;
    let mut response: WebauthnRegistrationResponse =
        serde_json::from_value(payload).map_err(|error| {
            AuthError::bad_request(format!("Invalid WebAuthn registration response: {error}"))
        })?;
    if let Some(name) = &body.name {
        response.name = Some(name.clone());
    }
    Ok(response)
}

fn parse_authentication_response(
    body: &VerifyAuthenticationRequest,
) -> AuthResult<WebauthnLoginResponse> {
    let payload = normalize_webauthn_response_payload(&body.response, "authentication response")?;
    serde_json::from_value(payload).map_err(|error| {
        AuthError::bad_request(format!("Invalid WebAuthn authentication response: {error}"))
    })
}

fn parse_transports(raw: Option<&str>) -> Option<Vec<String>> {
    raw.and_then(|value| serde_json::from_str::<Vec<String>>(value).ok())
        .filter(|values| !values.is_empty())
}

fn state_identifier(id: &str) -> String {
    format!("{PASSKEY_STATE_PREFIX}:{id}")
}

struct PasskeyStoreAdapter<'a, DB: DatabaseAdapter> {
    ctx: &'a AuthContext<DB>,
}

impl<'a, DB: DatabaseAdapter> PasskeyStoreAdapter<'a, DB> {
    fn new(ctx: &'a AuthContext<DB>) -> Self {
        Self { ctx }
    }

    fn map_store_error(error: AuthError) -> PasskeyError {
        PasskeyError::DatabaseError(error.to_string())
    }

    fn to_utc_timestamp(millis: i64) -> Result<chrono::DateTime<Utc>, PasskeyError> {
        Utc.timestamp_millis_opt(millis)
            .single()
            .ok_or_else(|| PasskeyError::InternalError("Invalid state expiration timestamp".into()))
    }

    async fn delete_states_with_identifier(&self, identifier: &str) -> passkey_server::Result<()> {
        loop {
            let Some(existing) = self
                .ctx
                .database
                .get_verification_by_identifier(identifier)
                .await
                .map_err(Self::map_store_error)?
            else {
                break;
            };

            self.ctx
                .database
                .delete_verification(existing.id())
                .await
                .map_err(Self::map_store_error)?;
        }

        Ok(())
    }
}

#[async_trait]
impl<'a, DB: DatabaseAdapter> PasskeyStore for PasskeyStoreAdapter<'a, DB> {
    async fn create_passkey(
        &self,
        user_id: String,
        cred_id: &str,
        public_key: &str,
        name: &str,
        counter: i64,
        _created_at: i64,
    ) -> passkey_server::Result<()> {
        let counter: u64 = u64::try_from(counter).map_err(|_| {
            PasskeyError::InternalError("Passkey counter cannot be negative".to_string())
        })?;

        self.ctx
            .database
            .create_passkey(CreatePasskey {
                user_id,
                name: name.to_string(),
                credential_id: cred_id.to_string(),
                public_key: public_key.to_string(),
                counter,
                device_type: "singleDevice".to_string(),
                backed_up: false,
                transports: None,
            })
            .await
            .map_err(Self::map_store_error)?;

        Ok(())
    }

    async fn get_passkey(&self, cred_id: &str) -> passkey_server::Result<Option<StoredPasskey>> {
        let passkey = self
            .ctx
            .database
            .get_passkey_by_credential_id(cred_id)
            .await
            .map_err(Self::map_store_error)?;

        passkey
            .map(|pk| {
                let counter = i64::try_from(pk.counter()).map_err(|_| {
                    PasskeyError::InternalError("Passkey counter exceeds i64 range".into())
                })?;
                let created_at = pk.created_at().timestamp_millis();

                Ok(StoredPasskey {
                    user_id: pk.user_id().to_string(),
                    cred_id: pk.credential_id().to_string(),
                    public_key: pk.public_key().to_string(),
                    name: pk.name().to_string(),
                    created_at,
                    last_used_at: created_at,
                    counter,
                })
            })
            .transpose()
    }

    async fn list_passkeys(&self, user_id: String) -> passkey_server::Result<Vec<StoredPasskey>> {
        let passkeys = self
            .ctx
            .database
            .list_passkeys_by_user(&user_id)
            .await
            .map_err(Self::map_store_error)?;

        passkeys
            .into_iter()
            .map(|pk| {
                let counter = i64::try_from(pk.counter()).map_err(|_| {
                    PasskeyError::InternalError("Passkey counter exceeds i64 range".into())
                })?;
                let created_at = pk.created_at().timestamp_millis();
                Ok(StoredPasskey {
                    user_id: pk.user_id().to_string(),
                    cred_id: pk.credential_id().to_string(),
                    public_key: pk.public_key().to_string(),
                    name: pk.name().to_string(),
                    created_at,
                    last_used_at: created_at,
                    counter,
                })
            })
            .collect()
    }

    async fn delete_passkey(&self, user_id: String, cred_id: &str) -> passkey_server::Result<()> {
        let passkey = self
            .ctx
            .database
            .get_passkey_by_credential_id(cred_id)
            .await
            .map_err(Self::map_store_error)?;

        if let Some(passkey) = passkey
            && passkey.user_id() == user_id
        {
            self.ctx
                .database
                .delete_passkey(passkey.id())
                .await
                .map_err(Self::map_store_error)?;
        }

        Ok(())
    }

    async fn update_passkey_counter(
        &self,
        cred_id: &str,
        new_counter: i64,
        _last_used_at: i64,
    ) -> passkey_server::Result<()> {
        let counter = u64::try_from(new_counter).map_err(|_| {
            PasskeyError::InternalError("Passkey counter cannot be negative".to_string())
        })?;

        let passkey = self
            .ctx
            .database
            .get_passkey_by_credential_id(cred_id)
            .await
            .map_err(Self::map_store_error)?
            .ok_or(PasskeyError::PasskeyNotFound)?;

        self.ctx
            .database
            .update_passkey_counter(passkey.id(), counter)
            .await
            .map_err(Self::map_store_error)?;

        Ok(())
    }

    async fn update_passkey_name(
        &self,
        cred_id: &str,
        new_name: &str,
    ) -> passkey_server::Result<()> {
        let passkey = self
            .ctx
            .database
            .get_passkey_by_credential_id(cred_id)
            .await
            .map_err(Self::map_store_error)?
            .ok_or(PasskeyError::PasskeyNotFound)?;

        self.ctx
            .database
            .update_passkey_name(passkey.id(), new_name)
            .await
            .map_err(Self::map_store_error)?;

        Ok(())
    }

    async fn save_state(
        &self,
        id: &str,
        state_json: &str,
        expires_at: i64,
    ) -> passkey_server::Result<()> {
        let identifier = state_identifier(id);
        self.delete_states_with_identifier(&identifier).await?;

        self.ctx
            .database
            .create_verification(CreateVerification {
                identifier,
                value: state_json.to_string(),
                expires_at: Self::to_utc_timestamp(expires_at)?,
            })
            .await
            .map_err(Self::map_store_error)?;

        Ok(())
    }

    async fn get_state(&self, id: &str) -> passkey_server::Result<Option<PasskeyState>> {
        let identifier = state_identifier(id);
        let verification = self
            .ctx
            .database
            .get_verification_by_identifier(&identifier)
            .await
            .map_err(Self::map_store_error)?;

        Ok(verification.map(|verification| PasskeyState {
            id: id.to_string(),
            state_json: verification.value().to_string(),
            expires_at: verification.expires_at().timestamp_millis(),
        }))
    }

    async fn delete_state(&self, id: &str) -> passkey_server::Result<()> {
        let identifier = state_identifier(id);
        self.delete_states_with_identifier(&identifier).await
    }
}

fn ensure_option_arrays(
    mut value: serde_json::Value,
    field_name: &str,
) -> AuthResult<serde_json::Value> {
    let object = value
        .as_object_mut()
        .ok_or_else(|| AuthError::internal("Passkey options should serialize as an object"))?;
    object
        .entry(field_name.to_string())
        .or_insert_with(|| serde_json::Value::Array(Vec::new()));
    Ok(value)
}

fn user_label(user: &impl AuthUser) -> (&str, &str) {
    let name = user
        .name()
        .unwrap_or_else(|| user.email().unwrap_or_else(|| user.id()));
    let username = user
        .email()
        .unwrap_or_else(|| user.name().unwrap_or_else(|| user.id()));
    (username, name)
}

pub(crate) async fn generate_register_options_core<DB: DatabaseAdapter>(
    user: &DB::User,
    authenticator_attachment: Option<&str>,
    config: &PasskeyConfig,
    ctx: &AuthContext<DB>,
) -> AuthResult<serde_json::Value> {
    let store = PasskeyStoreAdapter::new(ctx);
    let cfg = to_server_config(config);
    let (username, display_name) = user_label(user);

    let mut options: PublicKeyCredentialCreationOptions =
        start_registration(&store, user.id(), username, display_name, &cfg, now_ms())
            .await
            .map_err(to_auth_error)?;

    if let Some(attachment) = authenticator_attachment
        && let Some(selection) = options.authenticator_selection.as_mut()
    {
        selection.authenticator_attachment = Some(attachment.to_string());
    }

    let value = serde_json::to_value(options)?;
    ensure_option_arrays(value, "excludeCredentials")
}

pub(crate) async fn verify_registration_core<DB: DatabaseAdapter>(
    body: &VerifyRegistrationRequest,
    user: &DB::User,
    config: &PasskeyConfig,
    ctx: &AuthContext<DB>,
) -> AuthResult<PasskeyView> {
    let store = PasskeyStoreAdapter::new(ctx);
    let cfg = to_server_config(config);
    let response = parse_registration_response(body)?;
    let credential_id = response.id.clone();

    finish_registration(&store, user.id(), &cfg, response, now_ms())
        .await
        .map_err(to_auth_error)?;

    let created = if let Some(found) = ctx
        .database
        .get_passkey_by_credential_id(&credential_id)
        .await?
    {
        found
    } else {
        ctx.database
            .list_passkeys_by_user(user.id())
            .await?
            .into_iter()
            .next()
            .ok_or_else(|| AuthError::internal("Passkey was created but could not be loaded"))?
    };

    Ok(PasskeyView::from_entity(&created))
}

pub(crate) async fn generate_authenticate_options_core<DB: DatabaseAdapter>(
    maybe_user: Option<&DB::User>,
    config: &PasskeyConfig,
    ctx: &AuthContext<DB>,
) -> AuthResult<serde_json::Value> {
    let store = PasskeyStoreAdapter::new(ctx);
    let cfg = to_server_config(config);

    let mut options: PublicKeyCredentialRequestOptions = start_login(&store, &cfg, now_ms())
        .await
        .map_err(to_auth_error)?;

    if let Some(user) = maybe_user {
        let passkeys = ctx.database.list_passkeys_by_user(user.id()).await?;
        let allow_credentials: Vec<CredentialDescriptor> = passkeys
            .iter()
            .map(|pk| CredentialDescriptor {
                type_: "public-key".to_string(),
                id: pk.credential_id().to_string(),
                transports: parse_transports(pk.transports()),
            })
            .collect();
        options.allow_credentials = Some(allow_credentials);
    } else {
        options.allow_credentials = Some(Vec::new());
    }

    let value = serde_json::to_value(options)?;
    ensure_option_arrays(value, "allowCredentials")
}

pub(crate) async fn verify_authentication_core<DB: DatabaseAdapter>(
    body: &VerifyAuthenticationRequest,
    config: &PasskeyConfig,
    ip_address: Option<String>,
    user_agent: Option<String>,
    ctx: &AuthContext<DB>,
) -> AuthResult<(SessionUserResponse<DB::User, DB::Session>, String)> {
    let store = PasskeyStoreAdapter::new(ctx);
    let cfg = to_server_config(config);
    let response = parse_authentication_response(body)?;

    let user_id = finish_login(&store, &cfg, response, now_ms())
        .await
        .map_err(to_auth_error)?;

    let user = ctx
        .database
        .get_user_by_id(&user_id)
        .await?
        .ok_or(AuthError::UserNotFound)?;

    let audit_ip = ip_address.clone();
    let audit_user_agent = user_agent.clone();
    let session = ctx
        .session_manager()
        .create_session(&user, ip_address, user_agent)
        .await?;

    let token = session.token().to_string();
    write_sign_in_audit_in_background(
        ctx.clone(),
        "auth.sign_in.passkey",
        Some(user.id().to_string()),
        Some(token.clone()),
        Some("user_id".to_string()),
        Some(user.id().to_string()),
        true,
        None,
        audit_ip,
        audit_user_agent,
        None,
    );

    let response = SessionUserResponse {
        session: session_with_server_version(session),
        user,
    };
    Ok((response, token))
}

pub(crate) async fn list_user_passkeys_core<DB: DatabaseAdapter>(
    user: &DB::User,
    ctx: &AuthContext<DB>,
) -> AuthResult<Vec<PasskeyView>> {
    let passkeys = ctx.database.list_passkeys_by_user(user.id()).await?;
    Ok(passkeys.iter().map(PasskeyView::from_entity).collect())
}

pub(crate) async fn delete_passkey_core<DB: DatabaseAdapter>(
    body: &DeletePasskeyRequest,
    user: &DB::User,
    ctx: &AuthContext<DB>,
) -> AuthResult<StatusResponse> {
    let passkey = ctx
        .database
        .get_passkey_by_id(&body.id)
        .await?
        .ok_or_else(|| AuthError::not_found("Passkey not found"))?;

    if passkey.user_id() != user.id() {
        return Err(AuthError::not_found("Passkey not found"));
    }

    ctx.database.delete_passkey(&body.id).await?;

    Ok(StatusResponse { status: true })
}

pub(crate) async fn update_passkey_core<DB: DatabaseAdapter>(
    body: &UpdatePasskeyRequest,
    user: &DB::User,
    ctx: &AuthContext<DB>,
) -> AuthResult<PasskeyResponse> {
    let passkey = ctx
        .database
        .get_passkey_by_id(&body.id)
        .await?
        .ok_or_else(|| AuthError::not_found("Passkey not found"))?;

    if passkey.user_id() != user.id() {
        return Err(AuthError::not_found("Passkey not found"));
    }

    let updated = ctx
        .database
        .update_passkey_name(&body.id, &body.name)
        .await?;

    Ok(PasskeyResponse {
        passkey: PasskeyView::from_entity(&updated),
    })
}
