use std::env;

use chrono::{Duration, Utc};
use jsonwebtoken::{decode, encode, Algorithm, DecodingKey, EncodingKey, Header, Validation};
use rustelo_core_lib::auth::{Claims, Role, User};
use serde::{Deserialize, Serialize};
use uuid::Uuid;

#[derive(Clone)]
pub struct JwtService {
    encoding_key: EncodingKey,
    decoding_key: DecodingKey,
    algorithm: Algorithm,
    issuer: String,
    access_token_expires_in: Duration,
    refresh_token_expires_in: Duration,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct TokenPair {
    pub access_token: String,
    pub refresh_token: String,
    pub expires_in: i64,
    pub token_type: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct PendingGdprClaims {
    pub sub: String,
    pub token_type: String,
    pub exp: usize,
    pub iat: usize,
    pub iss: String,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct RefreshTokenClaims {
    pub sub: String,
    pub token_type: String,
    pub exp: usize,
    pub iat: usize,
    pub iss: String,
}

impl JwtService {
    pub fn new() -> Result<Self, Box<dyn std::error::Error>> {
        let secret = env::var("JWT_SECRET")
            .unwrap_or_else(|_| "your-super-secret-jwt-key-change-this-in-production".to_string());

        let issuer = env::var("JWT_ISSUER").unwrap_or_else(|_| "rustelo-auth".to_string());

        let access_token_expires_in = Duration::minutes(
            env::var("JWT_ACCESS_TOKEN_EXPIRES_IN")
                .unwrap_or_else(|_| "15".to_string())
                .parse()
                .unwrap_or(15),
        );

        let refresh_token_expires_in = Duration::days(
            env::var("JWT_REFRESH_TOKEN_EXPIRES_IN")
                .unwrap_or_else(|_| "7".to_string())
                .parse()
                .unwrap_or(7),
        );

        Ok(Self {
            encoding_key: EncodingKey::from_secret(secret.as_bytes()),
            decoding_key: DecodingKey::from_secret(secret.as_bytes()),
            algorithm: Algorithm::HS256,
            issuer,
            access_token_expires_in,
            refresh_token_expires_in,
        })
    }

    pub fn generate_token_pair(
        &self,
        user: &User,
    ) -> Result<TokenPair, Box<dyn std::error::Error>> {
        let access_token = self.generate_access_token(user)?;
        let refresh_token = self.generate_refresh_token(user)?;

        Ok(TokenPair {
            access_token,
            refresh_token,
            expires_in: self.access_token_expires_in.num_seconds(),
            token_type: "Bearer".to_string(),
        })
    }

    pub fn generate_access_token(&self, user: &User) -> Result<String, Box<dyn std::error::Error>> {
        let now = Utc::now();
        let expires_at = now + self.access_token_expires_in;

        let claims = Claims {
            sub: user.id.to_string(),
            email: user.email.clone(),
            roles: user.roles.clone(),
            exp: expires_at.timestamp() as usize,
            iat: now.timestamp() as usize,
            iss: self.issuer.clone(),
        };

        let header = Header::new(self.algorithm);
        let token = encode(&header, &claims, &self.encoding_key)?;
        Ok(token)
    }

    pub fn generate_refresh_token(
        &self,
        user: &User,
    ) -> Result<String, Box<dyn std::error::Error>> {
        let now = Utc::now();
        let expires_at = now + self.refresh_token_expires_in;

        let claims = RefreshTokenClaims {
            sub: user.id.to_string(),
            token_type: "refresh".to_string(),
            exp: expires_at.timestamp() as usize,
            iat: now.timestamp() as usize,
            iss: self.issuer.clone(),
        };

        let header = Header::new(self.algorithm);
        let token = encode(&header, &claims, &self.encoding_key)?;
        Ok(token)
    }

    pub fn verify_access_token(&self, token: &str) -> Result<Claims, Box<dyn std::error::Error>> {
        let mut validation = Validation::new(self.algorithm);
        validation.set_issuer(&[&self.issuer]);

        let token_data = decode::<Claims>(token, &self.decoding_key, &validation)?;
        Ok(token_data.claims)
    }

    pub fn verify_refresh_token(
        &self,
        token: &str,
    ) -> Result<RefreshTokenClaims, Box<dyn std::error::Error>> {
        let mut validation = Validation::new(self.algorithm);
        validation.set_issuer(&[&self.issuer]);

        let token_data = decode::<RefreshTokenClaims>(token, &self.decoding_key, &validation)?;

        // Verify it's actually a refresh token
        if token_data.claims.token_type != "refresh" {
            return Err("Invalid token type".into());
        }

        Ok(token_data.claims)
    }

    pub fn extract_bearer_token(auth_header: &str) -> Option<&str> {
        if auth_header.starts_with("Bearer ") {
            Some(&auth_header[7..])
        } else {
            None
        }
    }

    #[allow(dead_code)]
    pub fn is_token_expired(&self, token: &str) -> bool {
        match self.verify_access_token(token) {
            Ok(claims) => {
                let now = Utc::now().timestamp() as usize;
                claims.exp < now
            }
            Err(_) => true,
        }
    }

    #[allow(dead_code)]
    pub fn get_user_id_from_token(&self, token: &str) -> Result<Uuid, Box<dyn std::error::Error>> {
        let claims = self.verify_access_token(token)?;
        let user_id = Uuid::parse_str(&claims.sub)?;
        Ok(user_id)
    }

    #[allow(dead_code)]
    pub fn get_user_roles_from_token(
        &self,
        token: &str,
    ) -> Result<Vec<Role>, Box<dyn std::error::Error>> {
        let claims = self.verify_access_token(token)?;
        Ok(claims.roles)
    }

    pub fn generate_pending_gdpr_token(
        &self,
        email: &str,
    ) -> Result<String, Box<dyn std::error::Error>> {
        let now = Utc::now();
        let expires_at = now + Duration::hours(1);

        let claims = PendingGdprClaims {
            sub: email.to_string(),
            token_type: "pending_gdpr".to_string(),
            exp: expires_at.timestamp() as usize,
            iat: now.timestamp() as usize,
            iss: self.issuer.clone(),
        };

        let header = Header::new(self.algorithm);
        Ok(encode(&header, &claims, &self.encoding_key)?)
    }

    pub fn verify_pending_gdpr_token(
        &self,
        token: &str,
    ) -> Result<PendingGdprClaims, Box<dyn std::error::Error>> {
        let mut validation = Validation::new(self.algorithm);
        validation.set_issuer(&[&self.issuer]);

        let token_data = decode::<PendingGdprClaims>(token, &self.decoding_key, &validation)?;

        if token_data.claims.token_type != "pending_gdpr" {
            return Err("Invalid token type".into());
        }

        Ok(token_data.claims)
    }

    pub fn refresh_access_token(
        &self,
        refresh_token: &str,
        user: &User,
    ) -> Result<String, Box<dyn std::error::Error>> {
        // Verify the refresh token
        let refresh_claims = self.verify_refresh_token(refresh_token)?;

        // Verify the refresh token belongs to the user
        if refresh_claims.sub != user.id.to_string() {
            return Err("Invalid refresh token".into());
        }

        // Generate new access token
        self.generate_access_token(user)
    }

    #[allow(dead_code)]
    pub fn blacklist_token(&self, _token: &str) -> Result<(), Box<dyn std::error::Error>> {
        // In a production system, you would store blacklisted tokens in Redis or a
        // database For now, we'll just return Ok as tokens will naturally
        // expire TODO: Implement proper token blacklisting with Redis
        Ok(())
    }
}

impl Default for JwtService {
    fn default() -> Self {
        Self::new().expect("Failed to create JWT service")
    }
}

#[cfg(test)]
mod tests {
    use rustelo_core_lib::auth::{Role, User, UserProfile};

    use super::*;

    fn create_test_user() -> User {
        User {
            id: Uuid::new_v4(),
            email: "test@example.com".to_string(),
            username: "testuser".to_string(),
            display_name: Some("Test User".to_string()),
            avatar_url: None,
            roles: vec![Role::User],
            is_active: true,
            email_verified: true,
            created_at: Utc::now(),
            updated_at: Utc::now(),
            last_login: None,
            two_factor_enabled: false,
            profile: UserProfile::default(),
        }
    }

    #[test]
    fn test_jwt_service_creation() {
        let jwt_service = JwtService::new();
        assert!(jwt_service.is_ok());
    }

    #[test]
    fn test_generate_and_verify_access_token() {
        let jwt_service = JwtService::new().expect("Failed to create JWT service");
        let user = create_test_user();

        let token = jwt_service
            .generate_access_token(&user)
            .expect("Failed to generate token");
        let claims = jwt_service
            .verify_access_token(&token)
            .expect("Failed to verify token");

        assert_eq!(claims.sub, user.id.to_string());
        assert_eq!(claims.email, user.email);
        assert_eq!(claims.roles, user.roles);
    }

    #[test]
    fn test_generate_and_verify_refresh_token() {
        let jwt_service = JwtService::new().expect("Failed to create JWT service");
        let user = create_test_user();

        let token = jwt_service
            .generate_refresh_token(&user)
            .expect("Failed to generate token");
        let claims = jwt_service
            .verify_refresh_token(&token)
            .expect("Failed to verify token");

        assert_eq!(claims.sub, user.id.to_string());
        assert_eq!(claims.token_type, "refresh");
    }

    #[test]
    fn test_token_pair_generation() {
        let jwt_service = JwtService::new().expect("Failed to create JWT service");
        let user = create_test_user();

        let token_pair = jwt_service
            .generate_token_pair(&user)
            .expect("Failed to generate token pair");

        // Verify access token
        let access_claims = jwt_service
            .verify_access_token(&token_pair.access_token)
            .expect("Failed to verify access token");
        assert_eq!(access_claims.sub, user.id.to_string());

        // Verify refresh token
        let refresh_claims = jwt_service
            .verify_refresh_token(&token_pair.refresh_token)
            .expect("Failed to verify refresh token");
        assert_eq!(refresh_claims.sub, user.id.to_string());
    }

    #[test]
    fn test_extract_bearer_token() {
        let auth_header = "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
        let token = JwtService::extract_bearer_token(auth_header);
        assert_eq!(token, Some("eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9"));

        let invalid_header = "Token eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9";
        let token = JwtService::extract_bearer_token(invalid_header);
        assert_eq!(token, None);
    }

    #[test]
    fn test_refresh_access_token() {
        let jwt_service = JwtService::new().expect("Failed to create JWT service");
        let user = create_test_user();

        let refresh_token = jwt_service
            .generate_refresh_token(&user)
            .expect("Failed to generate refresh token");
        let new_access_token = jwt_service
            .refresh_access_token(&refresh_token, &user)
            .expect("Failed to refresh access token");

        let claims = jwt_service
            .verify_access_token(&new_access_token)
            .expect("Failed to verify new access token");
        assert_eq!(claims.sub, user.id.to_string());
    }
}
