#![allow(dead_code)]
use anyhow::Result;
use chrono::{Duration, Utc};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, Validation};
use serde::{Deserialize, Serialize};

use crate::config::JwtConfig;

// Token claims
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Claims {
    pub sub: String,
    pub exp: usize,
    pub iat: usize,
    pub user_id: String,
    pub email: String,
    pub token_version: i32,
}

impl Claims {
    /// Build access-token claims. `expiry` is the access-token lifetime and is
    /// taken from `JwtConfig` by callers (no longer hardcoded here).
    pub fn new(user_id: String, email: String, token_version: i32, expiry: Duration) -> Self {
        let now = Utc::now();
        let exp = now + expiry;

        Self {
            sub: user_id.clone(),
            exp: exp.timestamp() as usize,
            iat: now.timestamp() as usize,
            user_id,
            email,
            token_version,
        }
    }
}

// Refresh token claims (longer expiry)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RefreshClaims {
    pub sub: String,
    pub exp: usize,
    pub iat: usize,
    pub user_id: String,
    pub token_version: i32,
}

impl RefreshClaims {
    /// Build refresh-token claims. `expiry` is taken from `JwtConfig` by callers.
    pub fn new(user_id: String, token_version: i32, expiry: Duration) -> Self {
        let now = Utc::now();
        let exp = now + expiry;

        Self {
            sub: user_id.clone(),
            exp: exp.timestamp() as usize,
            iat: now.timestamp() as usize,
            user_id,
            token_version,
        }
    }
}

/// JWT Service for token generation and validation.
///
/// Pure crypto service: it signs/verifies tokens but does NOT track them. Token
/// persistence (refresh-token storage, revocation) is handled by
/// `RefreshTokenRepository` + the auth handlers; token-version staleness is
/// enforced in the JWT middleware via `TokenVersionCache`.
#[derive(Clone)]
pub struct JwtService {
    config: JwtConfig,
    encoding_key: EncodingKey,
    decoding_key: DecodingKey,
}

impl JwtService {
    pub fn new(config: JwtConfig) -> Self {
        let encoding_key = EncodingKey::from_secret(config.secret.as_ref());
        let decoding_key = DecodingKey::from_secret(config.secret.as_ref());

        Self {
            config,
            encoding_key,
            decoding_key,
        }
    }

    /// Generate access and refresh tokens. Both expiries are derived from the
    /// configured `JwtConfig` so callers don't need to thread durations through.
    pub fn generate_tokens(&self, claims: Claims) -> Result<(String, String)> {
        let access_token = encode(&Header::default(), &claims, &self.encoding_key)
            .map_err(|e| anyhow::anyhow!("Failed to encode access token: {}", e))?;

        let refresh_expiry = Duration::days(self.config.refresh_token_expiry_days);
        let refresh_claims =
            RefreshClaims::new(claims.user_id.clone(), claims.token_version, refresh_expiry);
        let refresh_token = encode(&Header::default(), &refresh_claims, &self.encoding_key)
            .map_err(|e| anyhow::anyhow!("Failed to encode refresh token: {}", e))?;

        Ok((access_token, refresh_token))
    }

    /// The configured refresh-token lifetime, as a chrono `Duration`.
    pub fn refresh_expiry(&self) -> Duration {
        Duration::days(self.config.refresh_token_expiry_days)
    }

    /// The configured access-token lifetime, as a chrono `Duration`.
    pub fn access_expiry(&self) -> Duration {
        Duration::minutes(self.config.access_token_expiry_minutes)
    }

    /// Validate access token (signature + expiry only). Callers are responsible
    /// for any `token_version` comparison against the user record.
    pub fn validate_token(&self, token: &str) -> Result<Claims> {
        let token_data = decode::<Claims>(token, &self.decoding_key, &Validation::default())
            .map_err(|e| anyhow::anyhow!("Invalid token: {}", e))?;

        Ok(token_data.claims)
    }

    /// Validate refresh token (signature + expiry only). Callers must still
    /// confirm the token exists, is not revoked, and has not expired in the
    /// `RefreshTokenRepository`.
    pub fn validate_refresh_token(&self, token: &str) -> Result<RefreshClaims> {
        let token_data = decode::<RefreshClaims>(token, &self.decoding_key, &Validation::default())
            .map_err(|e| anyhow::anyhow!("Invalid refresh token: {}", e))?;

        Ok(token_data.claims)
    }
}

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

    fn test_config() -> JwtConfig {
        JwtConfig {
            secret: "test-secret".to_string(),
            access_token_expiry_minutes: 15,
            refresh_token_expiry_days: 7,
        }
    }

    #[test]
    fn generate_then_validate_roundtrip() {
        let svc = JwtService::new(test_config());
        let claims = Claims::new(
            "user-1".to_string(),
            "u@example.com".to_string(),
            0,
            Duration::minutes(15),
        );
        let (access, refresh) = svc.generate_tokens(claims).unwrap();

        let access_claims = svc.validate_token(&access).unwrap();
        assert_eq!(access_claims.user_id, "user-1");
        assert_eq!(access_claims.token_version, 0);

        let refresh_claims = svc.validate_refresh_token(&refresh).unwrap();
        assert_eq!(refresh_claims.user_id, "user-1");
    }

    #[test]
    fn refresh_version_is_carried_through() {
        let svc = JwtService::new(test_config());
        let claims = Claims::new(
            "user-2".to_string(),
            "u2@example.com".to_string(),
            3,
            Duration::minutes(15),
        );
        let (_access, refresh) = svc.generate_tokens(claims).unwrap();
        let refresh_claims = svc.validate_refresh_token(&refresh).unwrap();
        assert_eq!(refresh_claims.token_version, 3);
    }
}
