// @generated — durable AuthMachine lifecycle marker contract
// Generated by `xtask protocol-codegen`
// Composition: auth_lease_bundle, Producer: auth_machine, Protocol: auth_lease_lifecycle_publication
// Durable marker source fields: phase->new_state, expires_at->expires_at, generation->credential_generation, credential_published_at_millis->credential_published_at_millis, identity fields: realm, binding, profile
// Durable marker relation: AuthLeaseCredentialPublication
#![allow(clippy::bool_comparison)]

use crate::auth::{PersistedTokens, TokenKey};
use crate::handles::{AuthLeasePhase, AuthLeaseSnapshot};

pub(crate) const METADATA_KEY: &str = "meerkat_auth_lifecycle";
pub(crate) const PREVIOUS_METADATA_KEY: &str = "meerkat_previous_metadata";
const FIELD_PUBLISHED: &str = "published";
const FIELD_VERSION: &str = "version";
const FIELD_AUTHORITY: &str = "authority";
const FIELD_PROTOCOL: &str = "protocol";
const FIELD_REALM: &str = "realm";
const FIELD_BINDING: &str = "binding";
const FIELD_PROFILE: &str = "profile";
const FIELD_PHASE: &str = "phase";
const FIELD_GENERATION: &str = "generation";
const FIELD_EXPIRES_AT: &str = "expires_at";
const FIELD_CREDENTIAL_PUBLISHED_AT_MILLIS: &str = "credential_published_at_millis";
pub(crate) const SCHEMA_VERSION: u64 = 4;
const AUTHORITY: &str = "auth_machine";
const PROTOCOL: &str = "auth_lease_lifecycle_publication";

#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) struct DurableAuthLifecycleMarker {
    pub token_key: TokenKey,
    pub phase: AuthLeasePhase,
    pub expires_at: u64,
    pub generation: u64,
    pub credential_published_at_millis: u64,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuthLeaseDurableMarkerRelation {
    Matches,
    TokenNewer,
    TokenStale,
    Invalid,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AuthLeaseDurableRestorePublication {
    token_key: TokenKey,
    phase: AuthLeasePhase,
    expires_at: u64,
    generation: u64,
    credential_published_at_millis: u64,
}

impl AuthLeaseDurableRestorePublication {
    pub fn token_key(&self) -> &TokenKey {
        &self.token_key
    }

    pub fn phase(&self) -> AuthLeasePhase {
        self.phase
    }

    pub fn expires_at(&self) -> u64 {
        self.expires_at
    }

    pub fn generation(&self) -> u64 {
        self.generation
    }

    pub fn credential_published_at_millis(&self) -> u64 {
        self.credential_published_at_millis
    }

    #[cfg(not(target_arch = "wasm32"))]
    fn from_marker_contract(
        token_key: TokenKey,
        phase: AuthLeasePhase,
        expires_at: u64,
        generation: u64,
        credential_published_at_millis: u64,
    ) -> Self {
        Self {
            token_key,
            phase,
            expires_at,
            generation,
            credential_published_at_millis,
        }
    }
}

fn phase_to_wire(phase: AuthLeasePhase) -> &'static str {
    match phase {
        AuthLeasePhase::Valid => "valid",
        AuthLeasePhase::Expiring => "expiring",
        AuthLeasePhase::Expired => "expired",
        AuthLeasePhase::Refreshing => "refreshing",
        AuthLeasePhase::ReauthRequired => "reauth_required",
        AuthLeasePhase::Released => "released",
    }
}

fn phase_from_wire(value: &serde_json::Value) -> Option<AuthLeasePhase> {
    match value.as_str()? {
        "valid" => Some(AuthLeasePhase::Valid),
        "expiring" => Some(AuthLeasePhase::Expiring),
        "expired" => Some(AuthLeasePhase::Expired),
        "refreshing" => Some(AuthLeasePhase::Refreshing),
        "reauth_required" => Some(AuthLeasePhase::ReauthRequired),
        "released" => Some(AuthLeasePhase::Released),
        _ => None,
    }
}

pub(crate) fn encode_marker_value(marker: DurableAuthLifecycleMarker) -> serde_json::Value {
    let mut map = serde_json::Map::new();
    map.insert(FIELD_PUBLISHED.to_string(), serde_json::Value::Bool(true));
    map.insert(
        FIELD_VERSION.to_string(),
        serde_json::Value::from(SCHEMA_VERSION),
    );
    map.insert(
        FIELD_AUTHORITY.to_string(),
        serde_json::Value::String(AUTHORITY.to_string()),
    );
    map.insert(
        FIELD_PROTOCOL.to_string(),
        serde_json::Value::String(PROTOCOL.to_string()),
    );
    map.insert(
        FIELD_REALM.to_string(),
        serde_json::Value::String(marker.token_key.realm.as_str().to_string()),
    );
    map.insert(
        FIELD_BINDING.to_string(),
        serde_json::Value::String(marker.token_key.binding.as_str().to_string()),
    );
    if let Some(profile) = marker.token_key.profile.as_ref() {
        map.insert(
            FIELD_PROFILE.to_string(),
            serde_json::Value::String(profile.as_str().to_string()),
        );
    } else {
        map.insert(FIELD_PROFILE.to_string(), serde_json::Value::Null);
    }
    map.insert(
        FIELD_PHASE.to_string(),
        serde_json::Value::String(phase_to_wire(marker.phase).to_string()),
    );
    map.insert(
        FIELD_GENERATION.to_string(),
        serde_json::Value::from(marker.generation),
    );
    map.insert(
        FIELD_EXPIRES_AT.to_string(),
        serde_json::Value::from(marker.expires_at),
    );
    map.insert(
        FIELD_CREDENTIAL_PUBLISHED_AT_MILLIS.to_string(),
        serde_json::Value::from(marker.credential_published_at_millis),
    );
    serde_json::Value::Object(map)
}

pub(crate) fn decode_marker_value(value: &serde_json::Value) -> Option<DurableAuthLifecycleMarker> {
    (value.get(FIELD_PUBLISHED)?.as_bool()? == true).then_some(())?;
    (value.get(FIELD_VERSION)?.as_u64()? == SCHEMA_VERSION).then_some(())?;
    (value.get(FIELD_AUTHORITY)?.as_str()? == AUTHORITY).then_some(())?;
    (value.get(FIELD_PROTOCOL)?.as_str()? == PROTOCOL).then_some(())?;
    let token_key = TokenKey::parse_with_profile(
        value.get(FIELD_REALM)?.as_str()?,
        value.get(FIELD_BINDING)?.as_str()?,
        value.get(FIELD_PROFILE).and_then(serde_json::Value::as_str),
    )
    .ok()?;
    Some(DurableAuthLifecycleMarker {
        token_key,
        phase: phase_from_wire(value.get(FIELD_PHASE)?)?,
        expires_at: value.get(FIELD_EXPIRES_AT)?.as_u64()?,
        generation: value.get(FIELD_GENERATION)?.as_u64()?,
        credential_published_at_millis: value
            .get(FIELD_CREDENTIAL_PUBLISHED_AT_MILLIS)?
            .as_u64()?,
    })
}

pub(crate) fn read_marker_from_metadata(
    metadata: &serde_json::Value,
) -> Option<DurableAuthLifecycleMarker> {
    decode_marker_value(metadata.get(METADATA_KEY)?)
}

pub(crate) fn metadata_has_valid_marker(metadata: &serde_json::Value) -> bool {
    read_marker_from_metadata(metadata).is_some()
}

pub(crate) fn metadata_with_marker(
    metadata: &serde_json::Value,
    marker: DurableAuthLifecycleMarker,
) -> serde_json::Value {
    let marker = encode_marker_value(marker);
    match metadata {
        serde_json::Value::Object(map) => {
            let mut map = map.clone();
            map.insert(METADATA_KEY.to_string(), marker);
            serde_json::Value::Object(map)
        }
        serde_json::Value::Null => {
            let mut map = serde_json::Map::new();
            map.insert(METADATA_KEY.to_string(), marker);
            serde_json::Value::Object(map)
        }
        other => {
            let mut map = serde_json::Map::new();
            map.insert(METADATA_KEY.to_string(), marker);
            map.insert(PREVIOUS_METADATA_KEY.to_string(), other.clone());
            serde_json::Value::Object(map)
        }
    }
}

#[cfg(not(target_arch = "wasm32"))]
pub(crate) fn restore_publication_from_metadata(
    metadata: &serde_json::Value,
    expected_key: &TokenKey,
) -> Option<AuthLeaseDurableRestorePublication> {
    let marker = read_marker_from_metadata(metadata)?;
    (&marker.token_key == expected_key).then_some(())?;
    Some(AuthLeaseDurableRestorePublication::from_marker_contract(
        marker.token_key,
        marker.phase,
        marker.expires_at,
        marker.generation,
        marker.credential_published_at_millis,
    ))
}

pub fn marker_payload_valid_for_tokens(tokens: &PersistedTokens, expected_key: &TokenKey) -> bool {
    let Some(marker) = read_marker_from_metadata(&tokens.metadata) else {
        return false;
    };
    marker.token_key == *expected_key
        && marker.expires_at == crate::persisted_token_expires_at_epoch_secs(tokens)
}

// Relation semantics are selected by the composition durable-marker contract.
pub fn marker_relation_for_tokens_and_snapshot(
    tokens: &PersistedTokens,
    snapshot: &AuthLeaseSnapshot,
    expected_key: &TokenKey,
) -> AuthLeaseDurableMarkerRelation {
    let Some(marker) = read_marker_from_metadata(&tokens.metadata) else {
        return AuthLeaseDurableMarkerRelation::Invalid;
    };
    if marker.token_key != *expected_key {
        return AuthLeaseDurableMarkerRelation::Invalid;
    }
    let token_expires_at = crate::persisted_token_expires_at_epoch_secs(tokens);
    if marker.expires_at != token_expires_at {
        return AuthLeaseDurableMarkerRelation::Invalid;
    }
    if !snapshot.credential_present {
        return AuthLeaseDurableMarkerRelation::TokenStale;
    }
    let generation_matches = marker.generation == snapshot.generation;
    let snapshot_expires_at = snapshot.expires_at.unwrap_or(u64::MAX);

    if let Some(snapshot_published_at) = snapshot.credential_published_at_millis {
        return match marker
            .credential_published_at_millis
            .cmp(&snapshot_published_at)
        {
            std::cmp::Ordering::Greater => AuthLeaseDurableMarkerRelation::TokenNewer,
            std::cmp::Ordering::Less => AuthLeaseDurableMarkerRelation::TokenStale,
            std::cmp::Ordering::Equal => {
                if token_expires_at == snapshot_expires_at && generation_matches {
                    AuthLeaseDurableMarkerRelation::Matches
                } else {
                    AuthLeaseDurableMarkerRelation::Invalid
                }
            }
        };
    }

    match token_expires_at.cmp(&snapshot_expires_at) {
        std::cmp::Ordering::Greater => AuthLeaseDurableMarkerRelation::TokenNewer,
        std::cmp::Ordering::Less => AuthLeaseDurableMarkerRelation::TokenStale,
        std::cmp::Ordering::Equal => {
            if generation_matches {
                AuthLeaseDurableMarkerRelation::Matches
            } else {
                AuthLeaseDurableMarkerRelation::Invalid
            }
        }
    }
}
