use chrono::{DateTime, Utc};

use crate::entity::{
    AuthAccount, AuthAccountMeta, AuthApiKey, AuthApiKeyMeta, AuthInvitation, AuthInvitationMeta,
    AuthMember, AuthMemberMeta, AuthOrganization, AuthOrganizationMeta, AuthPasskey,
    AuthPasskeyMeta, AuthSession, AuthSessionMeta, AuthTwoFactor, AuthTwoFactorMeta, AuthUser,
    AuthUserMeta, AuthVerification, AuthVerificationMeta,
};

use super::types::{Account, ApiKey, Passkey, Session, TwoFactor, User, Verification};
use super::types_org::{Invitation, InvitationStatus, Member, Organization};

/// Blanket conversion from any [`AuthUser`] implementor to the concrete [`User`] type.
///
/// This avoids hand-written `to_user()` helpers scattered across plugins.
impl<T: AuthUser> From<&T> for User {
    fn from(u: &T) -> Self {
        Self {
            id: u.id().to_owned(),
            name: u.name().map(str::to_owned),
            email: u.email().map(str::to_owned),
            email_verified: u.email_verified(),
            image: u.image().map(str::to_owned),
            created_at: u.created_at(),
            updated_at: u.updated_at(),
            username: u.username().map(str::to_owned),
            display_username: u.display_username().map(str::to_owned),
            two_factor_enabled: u.two_factor_enabled(),
            role: u.role().map(str::to_owned),
            banned: u.banned(),
            ban_reason: u.ban_reason().map(str::to_owned),
            ban_expires: u.ban_expires(),
            last_sign_in_at: u.last_sign_in_at(),
            metadata: u.metadata().clone(),
        }
    }
}

impl AuthUser for User {
    fn id(&self) -> &str {
        &self.id
    }
    fn email(&self) -> Option<&str> {
        self.email.as_deref()
    }
    fn name(&self) -> Option<&str> {
        self.name.as_deref()
    }
    fn email_verified(&self) -> bool {
        self.email_verified
    }
    fn image(&self) -> Option<&str> {
        self.image.as_deref()
    }
    fn created_at(&self) -> DateTime<Utc> {
        self.created_at
    }
    fn updated_at(&self) -> DateTime<Utc> {
        self.updated_at
    }
    fn username(&self) -> Option<&str> {
        self.username.as_deref()
    }
    fn display_username(&self) -> Option<&str> {
        self.display_username.as_deref()
    }
    fn two_factor_enabled(&self) -> bool {
        self.two_factor_enabled
    }
    fn role(&self) -> Option<&str> {
        self.role.as_deref()
    }
    fn banned(&self) -> bool {
        self.banned
    }
    fn ban_reason(&self) -> Option<&str> {
        self.ban_reason.as_deref()
    }
    fn ban_expires(&self) -> Option<DateTime<Utc>> {
        self.ban_expires
    }
    fn last_sign_in_at(&self) -> Option<DateTime<Utc>> {
        self.last_sign_in_at
    }
    fn metadata(&self) -> &serde_json::Value {
        &self.metadata
    }
}

impl AuthSession for Session {
    fn id(&self) -> &str {
        &self.id
    }
    fn expires_at(&self) -> DateTime<Utc> {
        self.expires_at
    }
    fn token(&self) -> &str {
        &self.token
    }
    fn created_at(&self) -> DateTime<Utc> {
        self.created_at
    }
    fn updated_at(&self) -> DateTime<Utc> {
        self.updated_at
    }
    fn ip_address(&self) -> Option<&str> {
        self.ip_address.as_deref()
    }
    fn user_agent(&self) -> Option<&str> {
        self.user_agent.as_deref()
    }
    fn user_id(&self) -> &str {
        &self.user_id
    }
    fn impersonated_by(&self) -> Option<&str> {
        self.impersonated_by.as_deref()
    }
    fn active_organization_id(&self) -> Option<&str> {
        self.active_organization_id.as_deref()
    }
    fn active(&self) -> bool {
        self.active
    }
}

impl AuthAccount for Account {
    fn id(&self) -> &str {
        &self.id
    }
    fn account_id(&self) -> &str {
        &self.account_id
    }
    fn provider_id(&self) -> &str {
        &self.provider_id
    }
    fn user_id(&self) -> &str {
        &self.user_id
    }
    fn access_token(&self) -> Option<&str> {
        self.access_token.as_deref()
    }
    fn refresh_token(&self) -> Option<&str> {
        self.refresh_token.as_deref()
    }
    fn id_token(&self) -> Option<&str> {
        self.id_token.as_deref()
    }
    fn access_token_expires_at(&self) -> Option<DateTime<Utc>> {
        self.access_token_expires_at
    }
    fn refresh_token_expires_at(&self) -> Option<DateTime<Utc>> {
        self.refresh_token_expires_at
    }
    fn scope(&self) -> Option<&str> {
        self.scope.as_deref()
    }
    fn password(&self) -> Option<&str> {
        self.password.as_deref()
    }
    fn created_at(&self) -> DateTime<Utc> {
        self.created_at
    }
    fn updated_at(&self) -> DateTime<Utc> {
        self.updated_at
    }
}

impl AuthOrganization for Organization {
    fn id(&self) -> &str {
        &self.id
    }
    fn name(&self) -> &str {
        &self.name
    }
    fn slug(&self) -> &str {
        &self.slug
    }
    fn logo(&self) -> Option<&str> {
        self.logo.as_deref()
    }
    fn metadata(&self) -> Option<&serde_json::Value> {
        self.metadata.as_ref()
    }
    fn created_at(&self) -> DateTime<Utc> {
        self.created_at
    }
    fn updated_at(&self) -> DateTime<Utc> {
        self.updated_at
    }
}

impl AuthMember for Member {
    fn id(&self) -> &str {
        &self.id
    }
    fn organization_id(&self) -> &str {
        &self.organization_id
    }
    fn user_id(&self) -> &str {
        &self.user_id
    }
    fn role(&self) -> &str {
        &self.role
    }
    fn created_at(&self) -> DateTime<Utc> {
        self.created_at
    }
}

impl AuthInvitation for Invitation {
    fn id(&self) -> &str {
        &self.id
    }
    fn organization_id(&self) -> &str {
        &self.organization_id
    }
    fn email(&self) -> &str {
        &self.email
    }
    fn role(&self) -> &str {
        &self.role
    }
    fn status(&self) -> &InvitationStatus {
        &self.status
    }
    fn inviter_id(&self) -> &str {
        &self.inviter_id
    }
    fn expires_at(&self) -> DateTime<Utc> {
        self.expires_at
    }
    fn created_at(&self) -> DateTime<Utc> {
        self.created_at
    }
}

impl AuthVerification for Verification {
    fn id(&self) -> &str {
        &self.id
    }
    fn identifier(&self) -> &str {
        &self.identifier
    }
    fn value(&self) -> &str {
        &self.value
    }
    fn expires_at(&self) -> DateTime<Utc> {
        self.expires_at
    }
    fn created_at(&self) -> DateTime<Utc> {
        self.created_at
    }
    fn updated_at(&self) -> DateTime<Utc> {
        self.updated_at
    }
}

impl AuthTwoFactor for TwoFactor {
    fn id(&self) -> &str {
        &self.id
    }
    fn secret(&self) -> &str {
        &self.secret
    }
    fn backup_codes(&self) -> Option<&str> {
        self.backup_codes.as_deref()
    }
    fn user_id(&self) -> &str {
        &self.user_id
    }
    fn created_at(&self) -> DateTime<Utc> {
        self.created_at
    }
    fn updated_at(&self) -> DateTime<Utc> {
        self.updated_at
    }
}

impl AuthApiKey for ApiKey {
    fn id(&self) -> &str {
        &self.id
    }
    fn name(&self) -> Option<&str> {
        self.name.as_deref()
    }
    fn start(&self) -> Option<&str> {
        self.start.as_deref()
    }
    fn prefix(&self) -> Option<&str> {
        self.prefix.as_deref()
    }
    fn key_hash(&self) -> &str {
        &self.key_hash
    }
    fn user_id(&self) -> &str {
        &self.user_id
    }
    fn refill_interval(&self) -> Option<i64> {
        self.refill_interval
    }
    fn refill_amount(&self) -> Option<i64> {
        self.refill_amount
    }
    fn last_refill_at(&self) -> Option<&str> {
        self.last_refill_at.as_deref()
    }
    fn enabled(&self) -> bool {
        self.enabled
    }
    fn rate_limit_enabled(&self) -> bool {
        self.rate_limit_enabled
    }
    fn rate_limit_time_window(&self) -> Option<i64> {
        self.rate_limit_time_window
    }
    fn rate_limit_max(&self) -> Option<i64> {
        self.rate_limit_max
    }
    fn request_count(&self) -> Option<i64> {
        self.request_count
    }
    fn remaining(&self) -> Option<i64> {
        self.remaining
    }
    fn last_request(&self) -> Option<&str> {
        self.last_request.as_deref()
    }
    fn expires_at(&self) -> Option<&str> {
        self.expires_at.as_deref()
    }
    fn created_at(&self) -> &str {
        &self.created_at
    }
    fn updated_at(&self) -> &str {
        &self.updated_at
    }
    fn permissions(&self) -> Option<&str> {
        self.permissions.as_deref()
    }
    fn metadata(&self) -> Option<&str> {
        self.metadata.as_deref()
    }
}

// -- Meta trait impls for built-in types --
// Empty impls use the default column/table names which match the built-in schema.
impl AuthUserMeta for User {}
impl AuthSessionMeta for Session {}
impl AuthAccountMeta for Account {}
impl AuthOrganizationMeta for Organization {}
impl AuthMemberMeta for Member {}
impl AuthInvitationMeta for Invitation {}
impl AuthVerificationMeta for Verification {}
impl AuthTwoFactorMeta for TwoFactor {}
impl AuthApiKeyMeta for ApiKey {}
impl AuthPasskeyMeta for Passkey {}

impl AuthPasskey for Passkey {
    fn id(&self) -> &str {
        &self.id
    }
    fn name(&self) -> &str {
        &self.name
    }
    fn public_key(&self) -> &str {
        &self.public_key
    }
    fn user_id(&self) -> &str {
        &self.user_id
    }
    fn credential_id(&self) -> &str {
        &self.credential_id
    }
    fn counter(&self) -> u64 {
        self.counter
    }
    fn device_type(&self) -> &str {
        &self.device_type
    }
    fn backed_up(&self) -> bool {
        self.backed_up
    }
    fn transports(&self) -> Option<&str> {
        self.transports.as_deref()
    }
    fn created_at(&self) -> DateTime<Utc> {
        self.created_at
    }
}

// Manual FromRow implementations for PostgreSQL
#[cfg(feature = "sqlx-postgres")]
mod postgres_impls {
    use super::*;
    use chrono::NaiveDateTime;
    use sqlx::postgres::PgRow;
    use sqlx::{FromRow, Row};
    use std::fmt::Write as _;
    use uuid::Uuid;

    fn parse_json_text(column: &str, value: &str) -> Result<serde_json::Value, sqlx::Error> {
        serde_json::from_str(value).map_err(|error| {
            sqlx::Error::Decode(Box::new(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("column {column:?} contained invalid JSON text: {error}"),
            )))
        })
    }

    fn decode_json_value(row: &PgRow, column: &str) -> Result<serde_json::Value, sqlx::Error> {
        match row.try_get::<sqlx::types::Json<serde_json::Value>, _>(column) {
            Ok(json) => Ok(json.0),
            Err(json_error) => match row.try_get::<String, _>(column) {
                Ok(text) => parse_json_text(column, &text),
                Err(_) => Err(json_error),
            },
        }
    }

    fn decode_optional_json_value(
        row: &PgRow,
        column: &str,
    ) -> Result<Option<serde_json::Value>, sqlx::Error> {
        match row.try_get::<Option<sqlx::types::Json<serde_json::Value>>, _>(column) {
            Ok(json) => Ok(json.map(|value| value.0)),
            Err(json_error) => match row.try_get::<Option<String>, _>(column) {
                Ok(Some(text)) => parse_json_text(column, &text).map(Some),
                Ok(None) => Ok(None),
                Err(_) => Err(json_error),
            },
        }
    }

    fn is_missing_column(error: &sqlx::Error) -> bool {
        matches!(error, sqlx::Error::ColumnNotFound(_))
    }

    fn decode_optional_i64_with_default(
        row: &PgRow,
        column: &str,
        default: Option<i64>,
    ) -> Result<Option<i64>, sqlx::Error> {
        match row.try_get::<Option<i64>, _>(column) {
            Ok(value) => Ok(value),
            Err(int_error) => {
                if let Ok(value) = row.try_get::<Option<i32>, _>(column) {
                    return Ok(value.map(i64::from).or(default));
                }

                match row.try_get::<Option<String>, _>(column) {
                    Ok(Some(value)) => value.trim().parse::<i64>().map(Some).map_err(|error| {
                        sqlx::Error::Decode(Box::new(std::io::Error::new(
                            std::io::ErrorKind::InvalidData,
                            format!("column {column:?} contained invalid integer text: {error}"),
                        )))
                    }),
                    Ok(None) => Ok(default),
                    Err(error) if is_missing_column(&error) => Ok(default),
                    Err(_) => Err(int_error),
                }
            }
        }
    }

    fn decode_optional_text_with_default(
        row: &PgRow,
        column: &str,
        default: Option<String>,
    ) -> Result<Option<String>, sqlx::Error> {
        match row.try_get::<Option<String>, _>(column) {
            Ok(value) => Ok(value),
            Err(text_error) => match row.try_get::<Option<Uuid>, _>(column) {
                Ok(value) => Ok(value.map(|uuid| uuid.to_string())),
                Err(error) if is_missing_column(&error) => Ok(default),
                Err(_) => Err(text_error),
            },
        }
    }

    fn decode_required_text(row: &PgRow, column: &str) -> Result<String, sqlx::Error> {
        match row.try_get::<String, _>(column) {
            Ok(value) => Ok(value),
            Err(text_error) => match row.try_get::<Uuid, _>(column) {
                Ok(value) => Ok(value.to_string()),
                Err(_) => Err(text_error),
            },
        }
    }

    fn bytes_to_lower_hex(bytes: &[u8]) -> String {
        let mut out = String::with_capacity(bytes.len() * 2);
        for byte in bytes {
            let _ = write!(&mut out, "{byte:02x}");
        }
        out
    }

    fn decode_key_hash_column(row: &PgRow, column: &str) -> Option<String> {
        if let Ok(value) = row.try_get::<String, _>(column) {
            return Some(value);
        }
        if let Ok(Some(value)) = row.try_get::<Option<String>, _>(column) {
            return Some(value);
        }
        if let Ok(value) = row.try_get::<Vec<u8>, _>(column) {
            return Some(bytes_to_lower_hex(&value));
        }
        if let Ok(Some(value)) = row.try_get::<Option<Vec<u8>>, _>(column) {
            return Some(bytes_to_lower_hex(&value));
        }
        None
    }

    fn decode_bool_with_default(
        row: &PgRow,
        column: &str,
        default: bool,
    ) -> Result<bool, sqlx::Error> {
        match row.try_get::<bool, _>(column) {
            Ok(value) => Ok(value),
            Err(bool_error) => match row.try_get::<Option<i64>, _>(column) {
                Ok(Some(value)) => Ok(value != 0),
                Ok(None) => Ok(default),
                Err(_) => match row.try_get::<Option<String>, _>(column) {
                    Ok(Some(value)) => {
                        let parsed = value.trim().to_ascii_lowercase();
                        match parsed.as_str() {
                            "1" | "true" | "t" | "yes" | "y" | "on" => Ok(true),
                            "0" | "false" | "f" | "no" | "n" | "off" => Ok(false),
                            _ => Err(sqlx::Error::Decode(Box::new(std::io::Error::new(
                                std::io::ErrorKind::InvalidData,
                                format!(
                                    "column {column:?} contained invalid boolean text: {value}"
                                ),
                            )))),
                        }
                    }
                    Ok(None) => Ok(default),
                    Err(error) if is_missing_column(&error) => Ok(default),
                    Err(_) => Err(bool_error),
                },
            },
        }
    }

    fn decode_required_datetime_rfc3339(row: &PgRow, column: &str) -> Result<String, sqlx::Error> {
        match row.try_get::<DateTime<Utc>, _>(column) {
            Ok(value) => Ok(value.to_rfc3339()),
            Err(datetime_error) => match row.try_get::<NaiveDateTime, _>(column) {
                Ok(value) => Ok(value.and_utc().to_rfc3339()),
                Err(_) => match decode_required_text(row, column) {
                    Ok(value) => Ok(value),
                    Err(_) => Err(datetime_error),
                },
            },
        }
    }

    fn decode_optional_datetime_rfc3339(
        row: &PgRow,
        column: &str,
    ) -> Result<Option<String>, sqlx::Error> {
        match row.try_get::<Option<DateTime<Utc>>, _>(column) {
            Ok(value) => Ok(value.map(|dt| dt.to_rfc3339())),
            Err(datetime_error) => match row.try_get::<Option<NaiveDateTime>, _>(column) {
                Ok(value) => Ok(value.map(|dt| dt.and_utc().to_rfc3339())),
                Err(_) => match decode_optional_text_with_default(row, column, None) {
                    Ok(value) => Ok(value),
                    Err(error) if is_missing_column(&error) => Ok(None),
                    Err(_) => Err(datetime_error),
                },
            },
        }
    }

    fn decode_optional_text_or_json(
        row: &PgRow,
        column: &str,
    ) -> Result<Option<String>, sqlx::Error> {
        match row.try_get::<Option<String>, _>(column) {
            Ok(value) => Ok(value),
            Err(text_error) => {
                match row.try_get::<Option<sqlx::types::Json<serde_json::Value>>, _>(column) {
                    Ok(value) => Ok(value.map(|json| json.0.to_string())),
                    Err(error) if is_missing_column(&error) => Ok(None),
                    Err(_) => Err(text_error),
                }
            }
        }
    }

    fn decode_api_key_hash(row: &PgRow) -> Result<String, sqlx::Error> {
        if let Some(value) = decode_key_hash_column(row, "key") {
            return Ok(value);
        }
        if let Some(value) = decode_key_hash_column(row, "key_hash") {
            return Ok(value);
        }
        if let Ok(value) = row.try_get::<Uuid, _>("key") {
            return Ok(value.to_string());
        }
        if let Ok(value) = row.try_get::<Uuid, _>("key_hash") {
            return Ok(value.to_string());
        }
        row.try_get::<String, _>("key")
    }

    impl FromRow<'_, PgRow> for User {
        fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
            Ok(Self {
                id: row.try_get("id")?,
                name: row.try_get("name")?,
                email: row.try_get("email")?,
                email_verified: row.try_get("email_verified")?,
                image: row.try_get("image")?,
                created_at: row.try_get("created_at")?,
                updated_at: row.try_get("updated_at")?,
                username: row.try_get("username")?,
                display_username: row.try_get("display_username")?,
                two_factor_enabled: row.try_get("two_factor_enabled").unwrap_or(false),
                role: row.try_get("role")?,
                banned: row.try_get("banned").unwrap_or(false),
                ban_reason: row.try_get("ban_reason")?,
                ban_expires: row.try_get("ban_expires")?,
                last_sign_in_at: row.try_get("last_sign_in_at").unwrap_or(None),
                metadata: decode_json_value(row, "metadata")?,
            })
        }
    }

    fn decode_datetime_utc(row: &PgRow, column: &str) -> Result<DateTime<Utc>, sqlx::Error> {
        match row.try_get::<DateTime<Utc>, _>(column) {
            Ok(value) => Ok(value),
            Err(datetime_error) => match row.try_get::<NaiveDateTime, _>(column) {
                Ok(naive) => Ok(naive.and_utc()),
                Err(_) => Err(datetime_error),
            },
        }
    }

    impl FromRow<'_, PgRow> for Session {
        fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
            Ok(Self {
                id: row.try_get("id")?,
                expires_at: row.try_get("expires_at")?,
                token: row.try_get("token")?,
                created_at: row.try_get("created_at")?,
                updated_at: row.try_get("updated_at")?,
                ip_address: row.try_get("ip_address")?,
                user_agent: row.try_get("user_agent")?,
                user_id: row.try_get("user_id")?,
                impersonated_by: row.try_get("impersonated_by")?,
                active_organization_id: row.try_get("active_organization_id")?,
                active: row.try_get("active").unwrap_or(true),
            })
        }
    }

    impl FromRow<'_, PgRow> for Account {
        fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
            Ok(Self {
                id: row.try_get("id")?,
                account_id: row.try_get("account_id")?,
                provider_id: row.try_get("provider_id")?,
                user_id: row.try_get("user_id")?,
                access_token: row.try_get("access_token")?,
                refresh_token: row.try_get("refresh_token")?,
                id_token: row.try_get("id_token")?,
                access_token_expires_at: row.try_get("access_token_expires_at")?,
                refresh_token_expires_at: row.try_get("refresh_token_expires_at")?,
                scope: row.try_get("scope")?,
                password: row.try_get("password")?,
                created_at: row.try_get("created_at")?,
                updated_at: row.try_get("updated_at")?,
            })
        }
    }

    impl FromRow<'_, PgRow> for Organization {
        fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
            Ok(Self {
                id: row.try_get("id")?,
                name: row.try_get("name")?,
                slug: row.try_get("slug")?,
                logo: row.try_get("logo")?,
                metadata: decode_optional_json_value(row, "metadata")?,
                created_at: row.try_get("created_at")?,
                updated_at: row.try_get("updated_at")?,
            })
        }
    }

    impl FromRow<'_, PgRow> for Member {
        fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
            Ok(Self {
                id: row.try_get("id")?,
                organization_id: row.try_get("organization_id")?,
                user_id: row.try_get("user_id")?,
                role: row.try_get("role")?,
                created_at: row.try_get("created_at")?,
            })
        }
    }

    impl FromRow<'_, PgRow> for Invitation {
        fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
            let status_str: String = row.try_get("status")?;
            Ok(Self {
                id: row.try_get("id")?,
                organization_id: row.try_get("organization_id")?,
                email: row.try_get("email")?,
                role: row.try_get("role")?,
                status: InvitationStatus::from(status_str),
                inviter_id: row.try_get("inviter_id")?,
                expires_at: decode_datetime_utc(row, "expires_at")?,
                created_at: decode_datetime_utc(row, "created_at")?,
            })
        }
    }

    impl FromRow<'_, PgRow> for Verification {
        fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
            Ok(Self {
                id: row.try_get("id")?,
                identifier: row.try_get("identifier")?,
                value: row.try_get("value")?,
                expires_at: row.try_get("expires_at")?,
                created_at: row.try_get("created_at")?,
                updated_at: row.try_get("updated_at")?,
            })
        }
    }

    impl FromRow<'_, PgRow> for TwoFactor {
        fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
            Ok(Self {
                id: row.try_get("id")?,
                secret: row.try_get("secret")?,
                backup_codes: row.try_get("backup_codes")?,
                user_id: row.try_get("user_id")?,
                created_at: row.try_get("created_at")?,
                updated_at: row.try_get("updated_at")?,
            })
        }
    }

    impl FromRow<'_, PgRow> for Passkey {
        fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
            let counter_raw = row.try_get::<i64, _>("counter")?;
            let counter = u64::try_from(counter_raw).map_err(|_| {
                sqlx::Error::Decode(Box::new(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    "passkey counter cannot be negative",
                )))
            })?;

            Ok(Self {
                id: row.try_get("id")?,
                name: row.try_get("name")?,
                public_key: row.try_get("public_key")?,
                user_id: row.try_get("user_id")?,
                credential_id: row.try_get("credential_id")?,
                counter,
                device_type: row.try_get("device_type")?,
                backed_up: row.try_get("backed_up")?,
                transports: row.try_get("transports")?,
                created_at: row.try_get("created_at")?,
            })
        }
    }

    impl FromRow<'_, PgRow> for ApiKey {
        fn from_row(row: &PgRow) -> Result<Self, sqlx::Error> {
            Ok(Self {
                id: decode_required_text(row, "id")?,
                name: decode_optional_text_with_default(row, "name", None)?,
                start: decode_optional_text_with_default(row, "start", None)?,
                prefix: decode_optional_text_with_default(row, "prefix", None)?,
                key_hash: decode_api_key_hash(row)?,
                user_id: decode_required_text(row, "user_id")?,
                refill_interval: decode_optional_i64_with_default(row, "refill_interval", None)?,
                refill_amount: decode_optional_i64_with_default(row, "refill_amount", None)?,
                last_refill_at: decode_optional_datetime_rfc3339(row, "last_refill_at")?,
                enabled: decode_bool_with_default(row, "enabled", true)?,
                rate_limit_enabled: decode_bool_with_default(row, "rate_limit_enabled", false)?,
                rate_limit_time_window: decode_optional_i64_with_default(
                    row,
                    "rate_limit_time_window",
                    None,
                )?,
                rate_limit_max: decode_optional_i64_with_default(row, "rate_limit_max", None)?,
                request_count: decode_optional_i64_with_default(row, "request_count", Some(0))?,
                remaining: decode_optional_i64_with_default(row, "remaining", None)?,
                last_request: decode_optional_datetime_rfc3339(row, "last_request")?,
                expires_at: decode_optional_datetime_rfc3339(row, "expires_at")?,
                created_at: decode_required_datetime_rfc3339(row, "created_at")?,
                updated_at: decode_required_datetime_rfc3339(row, "updated_at")?,
                permissions: decode_optional_text_or_json(row, "permissions")?,
                metadata: decode_optional_text_or_json(row, "metadata")?,
            })
        }
    }

    #[cfg(test)]
    mod tests {
        use super::{decode_json_value, decode_optional_json_value, parse_json_text};

        #[test]
        fn parse_json_text_accepts_valid_json() {
            let parsed = parse_json_text("metadata", r#"{"role":"admin"}"#)
                .expect("valid JSON text should parse");

            assert_eq!(parsed["role"], "admin");
        }

        #[test]
        fn parse_json_text_rejects_invalid_json() {
            let error =
                parse_json_text("metadata", "not-json").expect_err("invalid JSON text should fail");

            assert!(error.to_string().contains("metadata"));
        }

        #[test]
        fn decode_helpers_are_linked_for_row_decoders() {
            let _ = decode_json_value;
            let _ = decode_optional_json_value;
        }
    }
}
