use chrono::{Duration, Utc};
use jsonwebtoken::{DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use uuid::Uuid;

use crate::models::user::UserRole;

#[derive(Debug, Serialize, Deserialize)]
pub struct Claims {
    pub sub: Uuid,
    pub event_id: Uuid,
    pub role: UserRole,
    pub exp: i64,
    pub iat: i64,
    /// Random per-token identifier. Without it, two `create_token` calls in the
    /// same wall-clock second for the same (sub, role, event) produce identical
    /// JWT bytes — and identical sha256(token) hashes — which then collide on
    /// the `session.token_hash` UNIQUE constraint. The jti is ignored by the
    /// verifier but breaks the collision.
    #[serde(default)]
    pub jti: Uuid,
}

pub fn create_token(
    user_id: Uuid,
    event_id: Uuid,
    role: UserRole,
    secret: &str,
    expiry_days: i64,
) -> Result<String, jsonwebtoken::errors::Error> {
    let now = Utc::now();
    let claims = Claims {
        sub: user_id,
        event_id,
        role,
        iat: now.timestamp(),
        exp: (now + Duration::days(expiry_days)).timestamp(),
        jti: Uuid::new_v4(),
    };
    jsonwebtoken::encode(
        &Header::default(),
        &claims,
        &EncodingKey::from_secret(secret.as_bytes()),
    )
}

pub fn verify_token(token: &str, secret: &str) -> Result<Claims, jsonwebtoken::errors::Error> {
    // We deliberately do NOT enforce the JWT's own `exp`. The authoritative session
    // lifetime lives in the `session` row (`expires_at > NOW()`), which SLIDES forward on
    // every authenticated request (see `Session::touch_and_renew`). Enforcing the JWT's
    // fixed +Nd `exp` here would hard-log-out an actively-used client on day N+1 even
    // though its session was renewed — the "30-day cliff" from the review. With server-
    // side sliding sessions, the token is a signature-checked bearer credential and its
    // lifetime is revocable (logout / expiry / ban all delete the row), which is strictly
    // stronger than a stateless non-revocable exp.
    let mut validation = Validation::default();
    validation.validate_exp = false;
    let data = jsonwebtoken::decode::<Claims>(
        token,
        &DecodingKey::from_secret(secret.as_bytes()),
        &validation,
    )?;
    Ok(data.claims)
}

pub fn hash_token(token: &str) -> String {
    let mut hasher = Sha256::new();
    hasher.update(token.as_bytes());
    format!("{:x}", hasher.finalize())
}
