use base64::{
    Engine as _,
    engine::general_purpose::{URL_SAFE, URL_SAFE_NO_PAD},
};
use serde::Deserialize;
use std::env;

pub mod aggregate;
pub mod cli_token;
pub mod client;
pub mod config;
pub mod domain;
pub mod events;
pub mod plugin;
pub mod queue;
pub mod schema;
pub mod schema_version;
pub mod snapshots;
pub mod start;
pub mod system;
pub mod tenant;
pub mod token;
pub mod upgrade;
pub mod watch;

pub(crate) fn resolve_actor_name(
    explicit: Option<&str>,
    token: Option<&str>,
    fallback: &str,
) -> String {
    if let Some(value) = explicit.and_then(trimmed_non_empty) {
        return value;
    }

    if let Some(value) = token.and_then(actor_from_jwt) {
        return value;
    }

    env::var("USER")
        .or_else(|_| env::var("USERNAME"))
        .ok()
        .and_then(|value| trimmed_non_empty(value.as_str()))
        .unwrap_or_else(|| fallback.to_string())
}

fn actor_from_jwt(token: &str) -> Option<String> {
    let mut segments = token.trim().split('.');
    segments.next()?;
    let payload = segments.next()?;
    segments.next()?;

    let bytes = decode_jwt_payload(payload)?;
    let claims: JwtActorFragment = serde_json::from_slice(&bytes).ok()?;
    trimmed_non_empty(&claims.user).or_else(|| trimmed_non_empty(&claims.sub))
}

fn decode_jwt_payload(segment: &str) -> Option<Vec<u8>> {
    URL_SAFE_NO_PAD
        .decode(segment)
        .or_else(|_| {
            let mut padded = segment.to_string();
            while padded.len() % 4 != 0 {
                padded.push('=');
            }
            URL_SAFE.decode(padded.as_bytes())
        })
        .ok()
}

fn trimmed_non_empty(input: &str) -> Option<String> {
    let trimmed = input.trim();
    if trimmed.is_empty() {
        None
    } else {
        Some(trimmed.to_string())
    }
}

#[derive(Debug, Deserialize)]
struct JwtActorFragment {
    #[serde(default)]
    user: String,
    #[serde(default)]
    sub: String,
}

pub(crate) fn is_lock_error_message(message: &str) -> bool {
    eventdbx::store::is_lock_error_message(message)
}

pub(crate) fn is_lock_error(err: &eventdbx::error::EventError) -> bool {
    match err {
        eventdbx::error::EventError::Storage(message) => is_lock_error_message(message),
        _ => false,
    }
}
