use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use surrealdb::types::{RecordId, SurrealValue};
use uuid::Uuid;

/// Session model for managing user sessions and refresh tokens
#[derive(Debug, Clone, Serialize, Deserialize, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
pub struct Session {
    pub id: Option<RecordId>,
    pub session_id: Uuid,
    pub user_id: Uuid,
    pub refresh_token: String,
    pub is_active: bool,
    pub created_at: DateTime<Utc>,
    pub expires_at: DateTime<Utc>,
    pub last_used: DateTime<Utc>,
    pub client_info: Option<ClientInfo>,
}

/// Client information for session tracking
#[derive(Debug, Clone, Serialize, Deserialize, SurrealValue)]
#[surreal(crate = "surrealdb::types")]
pub struct ClientInfo {
    pub user_agent: Option<String>,
    pub ip_address: Option<String>,
    pub device_type: Option<String>,
}

/// Session response
#[derive(Debug, Serialize)]
pub struct SessionResponse {
    pub id: Uuid,
    pub user_id: Uuid,
    pub is_active: bool,
    pub created_at: DateTime<Utc>,
    pub expires_at: DateTime<Utc>,
    pub last_used: DateTime<Utc>,
    pub client_info: Option<ClientInfo>,
}

impl From<Session> for SessionResponse {
    fn from(session: Session) -> Self {
        Self {
            id: session.session_id,
            user_id: session.user_id,
            is_active: session.is_active,
            created_at: session.created_at,
            expires_at: session.expires_at,
            last_used: session.last_used,
            client_info: session.client_info,
        }
    }
}

impl Session {
    /// Create a new session
    pub fn new(
        user_id: Uuid,
        refresh_token: String,
        duration_hours: i64,
        client_info: Option<ClientInfo>,
    ) -> Self {
        let now = Utc::now();
        Self {
            id: None,
            session_id: Uuid::new_v4(),
            user_id,
            refresh_token,
            is_active: true,
            created_at: now,
            expires_at: now + chrono::Duration::hours(duration_hours),
            last_used: now,
            client_info,
        }
    }

    /// Check if session is expired
    pub fn is_expired(&self) -> bool {
        Utc::now() > self.expires_at
    }

    /// Update last used timestamp
    pub fn update_last_used(&mut self) {
        self.last_used = Utc::now();
    }

    /// Invalidate session
    pub fn invalidate(&mut self) {
        self.is_active = false;
    }

    /// Extend session expiration
    pub fn extend_expiration(&mut self, duration_hours: i64) {
        self.expires_at = Utc::now() + chrono::Duration::hours(duration_hours);
        self.update_last_used();
    }
}

/// JWT Claims structure
#[derive(Debug, Serialize, Deserialize)]
pub struct JwtClaims {
    pub sub: String,        // Subject (user_id)
    pub exp: i64,           // Expiration timestamp
    pub iat: i64,           // Issued at timestamp
    pub iss: String,        // Issuer
    pub aud: String,        // Audience
    pub session_id: String, // Session ID
    pub roles: Vec<String>, // User roles

    #[serde(skip_serializing_if = "Option::is_none")]
    pub mfa_verified: Option<bool>, // MFA verification status
}

impl JwtClaims {
    /// Create new JWT claims
    pub fn new(
        user_id: Uuid,
        session_id: Uuid,
        roles: Vec<String>,
        expiration_hours: i64,
        issuer: String,
        audience: String,
    ) -> Self {
        let now = Utc::now();
        Self {
            sub: user_id.to_string(),
            exp: (now + chrono::Duration::hours(expiration_hours)).timestamp(),
            iat: now.timestamp(),
            iss: issuer,
            aud: audience,
            session_id: session_id.to_string(),
            roles,
            mfa_verified: None, // Default to None, set explicitly when MFA is verified
        }
    }

    /// Check if token is expired
    pub fn is_expired(&self) -> bool {
        Utc::now().timestamp() > self.exp
    }
}

/// Token response for authentication
#[derive(Debug, Serialize)]
pub struct TokenResponse {
    pub access_token: String,
    pub refresh_token: String,
    pub token_type: String,
    pub expires_in: i64,
    pub session_id: String,
}

impl TokenResponse {
    pub fn new(
        access_token: String,
        refresh_token: String,
        expires_in: i64,
        session_id: String,
    ) -> Self {
        Self {
            access_token,
            refresh_token,
            token_type: "Bearer".to_string(),
            expires_in,
            session_id,
        }
    }
}

/// Refresh token request
#[derive(Debug, Deserialize)]
pub struct RefreshTokenRequest {
    pub refresh_token: String,
}

/// Logout request
#[derive(Debug, Deserialize)]
pub struct LogoutRequest {
    pub session_id: Option<String>,
    pub all_sessions: Option<bool>,
}
