//! Temporal authority revalidation.
//!
//! Authority is time-relative: a signature can be valid for the event time
//! and still be invalid for current reasoning after revocation, principal
//! removal, trust downgrade, or trust-review expiry.

use chrono::{DateTime, Utc};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};

use crate::{
    compose_policy_outcomes, CoreError, CoreResult, PolicyContribution, PolicyDecision,
    PolicyOutcome,
};
use crate::{FailingEdge, ProofEdgeFailure, ProofEdgeKind};

/// Key lifecycle state from ADR 0023.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum KeyLifecycleState {
    /// Key may sign new material from this effective time.
    Active,
    /// Key may not sign new material after this effective time.
    Retired,
    /// Key may not sign new material after this effective time.
    Revoked,
}

/// Principal trust tier from ADR 0019.
#[derive(
    Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, JsonSchema,
)]
#[serde(rename_all = "snake_case")]
pub enum TrustTier {
    /// No high-impact authority.
    Untrusted,
    /// Audit-visible only.
    Observed,
    /// Operator-approved for bounded automation.
    Verified,
    /// Human root of trust for this deployment scope.
    Operator,
}

/// Reason a temporal authority check was annotated, downgraded, or failed.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum TemporalAuthorityReason {
    /// Key had not been activated at event time.
    SignedBeforeActivation,
    /// Event was signed at or after the key revocation effective time.
    SignedAfterRevocation,
    /// Event was signed at or after the key retirement effective time.
    SignedAfterRetirement,
    /// Key was revoked after the event, so historical evidence exists but
    /// current reasoning is downgraded.
    RevokedAfterSigning,
    /// Key was retired after the event; historical verification remains valid.
    HistoricalRetiredKey,
    /// Principal trust tier fell below the required gate after signing.
    TrustTierDowngraded,
    /// Principal did not meet the required trust tier at event time.
    InsufficientTrustAtSigning,
    /// Principal was removed after signing.
    PrincipalRemoved,
    /// Principal trust review has expired.
    TrustReviewExpired,
    /// Key timeline was missing.
    KeyUnknown,
    /// Principal timeline was missing.
    PrincipalUnknown,
}

impl TemporalAuthorityReason {
    /// Stable wire string for diagnostics and proof-edge reasons.
    #[must_use]
    pub const fn wire_str(self) -> &'static str {
        match self {
            Self::SignedBeforeActivation => "signed_before_activation",
            Self::SignedAfterRevocation => "signed_after_revocation",
            Self::SignedAfterRetirement => "signed_after_retirement",
            Self::RevokedAfterSigning => "revoked_after_signing",
            Self::HistoricalRetiredKey => "historical_retired_key",
            Self::TrustTierDowngraded => "trust_tier_downgraded",
            Self::InsufficientTrustAtSigning => "insufficient_trust_at_signing",
            Self::PrincipalRemoved => "principal_removed",
            Self::TrustReviewExpired => "trust_review_expired",
            Self::KeyUnknown => "key_unknown",
            Self::PrincipalUnknown => "principal_unknown",
        }
    }
}

/// Evidence used for temporal authority revalidation.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct TemporalAuthorityEvidence {
    /// Key identifier carried by the attestation.
    pub key_id: String,
    /// Principal bound to the key, when known.
    pub principal_id: Option<String>,
    /// Signed event/audit time.
    pub event_time: DateTime<Utc>,
    /// Verification time.
    pub now: DateTime<Utc>,
    /// Key activation effective time.
    pub key_activated_at: Option<DateTime<Utc>>,
    /// Key retirement effective time.
    pub key_retired_at: Option<DateTime<Utc>>,
    /// Key revocation effective time.
    pub key_revoked_at: Option<DateTime<Utc>>,
    /// Trust tier at event time.
    pub trust_tier_at_event_time: Option<TrustTier>,
    /// Current trust tier.
    pub current_trust_tier: Option<TrustTier>,
    /// Effective time for the current trust tier.
    pub current_trust_tier_effective_at: Option<DateTime<Utc>>,
    /// Minimum trust tier required for current use.
    pub minimum_trust_tier: TrustTier,
    /// Principal removal time, when removed.
    pub principal_removed_at: Option<DateTime<Utc>>,
    /// Principal trust review deadline.
    pub trust_review_due_at: Option<DateTime<Utc>>,
}

/// Temporal authority check result.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct TemporalAuthorityReport {
    /// Key identifier carried by the attestation.
    pub key_id: String,
    /// Principal bound to the key, when known.
    pub principal_id: Option<String>,
    /// Signed event/audit time.
    pub event_time: DateTime<Utc>,
    /// Verification time.
    pub now: DateTime<Utc>,
    /// Whether the key/principal was valid for the signed event time.
    pub valid_at_event_time: bool,
    /// Whether this authority may condition current reasoning.
    pub valid_now: bool,
    /// First time after signing that invalidated current use.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub invalidated_after: Option<DateTime<Utc>>,
    /// Stable authority annotations and failure reasons.
    pub reasons: Vec<TemporalAuthorityReason>,
    /// Trust tier at event time.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub trust_tier_at_event_time: Option<TrustTier>,
    /// Current trust tier.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub current_trust_tier: Option<TrustTier>,
}

impl TemporalAuthorityReport {
    /// Convert a failed current-use result into a proof failing edge.
    #[must_use]
    pub fn current_use_failing_edge(&self, target_ref: impl Into<String>) -> Option<FailingEdge> {
        if self.valid_now {
            return None;
        }

        let reason = self
            .reasons
            .iter()
            .map(|reason| reason.wire_str())
            .collect::<Vec<_>>()
            .join(",");
        Some(FailingEdge::broken(
            ProofEdgeKind::AuthorityFold,
            target_ref,
            self.key_id.clone(),
            ProofEdgeFailure::AuthorityMismatch,
            reason,
        ))
    }

    /// Whether this report contains a specific reason.
    #[must_use]
    pub fn has_reason(&self, reason: TemporalAuthorityReason) -> bool {
        self.reasons.contains(&reason)
    }

    /// Derive the ADR 0026 policy decision for current use of this temporal
    /// authority.
    #[must_use]
    pub fn policy_decision(&self) -> PolicyDecision {
        let outcome = if self.valid_now {
            PolicyOutcome::Allow
        } else if self.valid_at_event_time {
            PolicyOutcome::Quarantine
        } else {
            PolicyOutcome::Reject
        };
        let reason = if self.valid_now {
            "temporal authority is valid for current use"
        } else if self.valid_at_event_time {
            "temporal authority remains historical evidence but is invalid for current use"
        } else {
            "temporal authority was invalid at event time"
        };
        compose_policy_outcomes(
            vec![
                PolicyContribution::new("authority.temporal.current_use", outcome, reason)
                    .expect("static policy contribution is valid"),
            ],
            None,
        )
    }

    /// Fail closed before this temporal authority report is consumed for
    /// current reasoning authority.
    pub fn require_current_use_allowed(&self) -> CoreResult<()> {
        let policy = self.policy_decision();
        match policy.final_outcome {
            PolicyOutcome::Reject | PolicyOutcome::Quarantine => {
                Err(CoreError::Validation(format!(
                    "temporal authority current use blocked by policy outcome {:?}",
                    policy.final_outcome
                )))
            }
            PolicyOutcome::Allow | PolicyOutcome::Warn | PolicyOutcome::BreakGlass => Ok(()),
        }
    }
}

/// Revalidate temporal authority using ADR 0023 and ADR 0019 semantics.
#[must_use]
pub fn revalidate_temporal_authority(
    evidence: TemporalAuthorityEvidence,
) -> TemporalAuthorityReport {
    let mut valid_at_event_time = true;
    let mut valid_now = true;
    let mut invalidated_after = None;
    let mut reasons = Vec::new();

    match evidence.key_activated_at {
        Some(activated_at) if evidence.event_time < activated_at => {
            valid_at_event_time = false;
            valid_now = false;
            reasons.push(TemporalAuthorityReason::SignedBeforeActivation);
        }
        Some(_) => {}
        None => {
            valid_at_event_time = false;
            valid_now = false;
            reasons.push(TemporalAuthorityReason::KeyUnknown);
        }
    }

    if let Some(revoked_at) = evidence.key_revoked_at {
        if evidence.event_time >= revoked_at {
            valid_at_event_time = false;
            valid_now = false;
            reasons.push(TemporalAuthorityReason::SignedAfterRevocation);
        } else if evidence.now >= revoked_at {
            valid_now = false;
            invalidated_after = min_time(invalidated_after, revoked_at);
            reasons.push(TemporalAuthorityReason::RevokedAfterSigning);
        }
    }

    if let Some(retired_at) = evidence.key_retired_at {
        if evidence.event_time >= retired_at {
            valid_at_event_time = false;
            valid_now = false;
            reasons.push(TemporalAuthorityReason::SignedAfterRetirement);
        } else if evidence.now >= retired_at {
            reasons.push(TemporalAuthorityReason::HistoricalRetiredKey);
        }
    }

    match evidence.trust_tier_at_event_time {
        Some(tier) if tier < evidence.minimum_trust_tier => {
            valid_at_event_time = false;
            valid_now = false;
            reasons.push(TemporalAuthorityReason::InsufficientTrustAtSigning);
        }
        Some(_) => {}
        None => {
            valid_at_event_time = false;
            valid_now = false;
            reasons.push(TemporalAuthorityReason::PrincipalUnknown);
        }
    }

    match evidence.current_trust_tier {
        Some(current) if current < evidence.minimum_trust_tier => {
            valid_now = false;
            if let Some(changed_at) = evidence.current_trust_tier_effective_at {
                invalidated_after = min_time(invalidated_after, changed_at);
            }
            reasons.push(TemporalAuthorityReason::TrustTierDowngraded);
        }
        Some(_) => {}
        None => {
            valid_now = false;
            reasons.push(TemporalAuthorityReason::PrincipalUnknown);
        }
    }

    if let Some(removed_at) = evidence.principal_removed_at {
        if evidence.now >= removed_at {
            valid_now = false;
            invalidated_after = min_time(invalidated_after, removed_at);
            reasons.push(TemporalAuthorityReason::PrincipalRemoved);
        }
    }

    if let Some(review_due_at) = evidence.trust_review_due_at {
        if evidence.now > review_due_at {
            valid_now = false;
            invalidated_after = min_time(invalidated_after, review_due_at);
            reasons.push(TemporalAuthorityReason::TrustReviewExpired);
        }
    }

    TemporalAuthorityReport {
        key_id: evidence.key_id,
        principal_id: evidence.principal_id,
        event_time: evidence.event_time,
        now: evidence.now,
        valid_at_event_time,
        valid_now: valid_at_event_time && valid_now,
        invalidated_after,
        reasons,
        trust_tier_at_event_time: evidence.trust_tier_at_event_time,
        current_trust_tier: evidence.current_trust_tier,
    }
}

fn min_time(current: Option<DateTime<Utc>>, candidate: DateTime<Utc>) -> Option<DateTime<Utc>> {
    Some(current.map_or(candidate, |current| current.min(candidate)))
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::TimeZone;

    fn at(day: u32) -> DateTime<Utc> {
        Utc.with_ymd_and_hms(2026, 1, day, 12, 0, 0).unwrap()
    }

    fn evidence() -> TemporalAuthorityEvidence {
        TemporalAuthorityEvidence {
            key_id: "key_1".into(),
            principal_id: Some("principal_1".into()),
            event_time: at(2),
            now: at(4),
            key_activated_at: Some(at(1)),
            key_retired_at: None,
            key_revoked_at: None,
            trust_tier_at_event_time: Some(TrustTier::Operator),
            current_trust_tier: Some(TrustTier::Operator),
            current_trust_tier_effective_at: Some(at(1)),
            minimum_trust_tier: TrustTier::Verified,
            principal_removed_at: None,
            trust_review_due_at: None,
        }
    }

    #[test]
    fn revoked_after_signing_is_historical_but_not_valid_now() {
        let mut evidence = evidence();
        evidence.key_revoked_at = Some(at(3));

        let report = revalidate_temporal_authority(evidence);

        assert!(report.valid_at_event_time);
        assert!(!report.valid_now);
        assert_eq!(report.invalidated_after, Some(at(3)));
        assert!(report.has_reason(TemporalAuthorityReason::RevokedAfterSigning));
    }

    #[test]
    fn signed_after_revocation_is_invalid_at_event_time() {
        let mut evidence = evidence();
        evidence.event_time = at(4);
        evidence.key_revoked_at = Some(at(3));

        let report = revalidate_temporal_authority(evidence);

        assert!(!report.valid_at_event_time);
        assert!(!report.valid_now);
        assert!(report.has_reason(TemporalAuthorityReason::SignedAfterRevocation));
    }

    #[test]
    fn trust_tier_downgrade_invalidates_current_use() {
        let mut evidence = evidence();
        evidence.current_trust_tier = Some(TrustTier::Observed);

        let report = revalidate_temporal_authority(evidence);

        assert!(report.valid_at_event_time);
        assert!(!report.valid_now);
        assert!(report.has_reason(TemporalAuthorityReason::TrustTierDowngraded));
        assert!(report.current_use_failing_edge("principle:1").is_some());
        assert_eq!(
            report.policy_decision().final_outcome,
            PolicyOutcome::Quarantine
        );
        assert!(report.require_current_use_allowed().is_err());
    }

    #[test]
    fn invalid_at_event_time_maps_to_policy_reject() {
        let mut evidence = evidence();
        evidence.event_time = at(4);
        evidence.key_revoked_at = Some(at(3));

        let report = revalidate_temporal_authority(evidence);

        assert_eq!(
            report.policy_decision().final_outcome,
            PolicyOutcome::Reject
        );
        assert!(report.require_current_use_allowed().is_err());
    }

    #[test]
    fn currently_valid_authority_maps_to_policy_allow() {
        let report = revalidate_temporal_authority(evidence());

        assert_eq!(report.policy_decision().final_outcome, PolicyOutcome::Allow);
        report
            .require_current_use_allowed()
            .expect("currently valid authority supports current use");
    }
}
