use indieweb::standards::indieauth::{ClientId, RedirectUri, Scopes};
use url::Url;

/// Pending authorization request awaiting user consent
#[derive(Clone, Debug)]
pub struct PendingAuthorization {
    pub id: String,
    pub client_id: ClientId,
    pub redirect_uri: RedirectUri,
    pub code_challenge: String,
    pub code_challenge_method: String,
    pub state: String,
    pub me: Option<Url>,
    pub scope: Scopes,
    pub user_me: Url,
    pub user_name: String,
    pub created_at: std::time::Instant,
    pub expires_at: std::time::Instant,
}

impl PendingAuthorization {
    pub fn new(
        id: String,
        client_id: ClientId,
        redirect_uri: RedirectUri,
        code_challenge: String,
        code_challenge_method: String,
        state: String,
        me: Option<Url>,
        scope: Scopes,
        user_me: Url,
        user_name: String,
        expires_in: std::time::Duration,
    ) -> Self {
        let now = std::time::Instant::now();
        Self {
            id,
            client_id,
            redirect_uri,
            code_challenge,
            code_challenge_method,
            state,
            me,
            scope,
            user_me,
            user_name,
            created_at: now,
            expires_at: now + expires_in,
        }
    }

    pub fn is_expired(&self) -> bool {
        std::time::Instant::now() >= self.expires_at
    }
}

/// Authorization code data stored by the server
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct AuthorizationCode {
    pub code: String,
    pub client_id: ClientId,
    pub redirect_uri: RedirectUri,
    pub code_challenge: String,
    pub code_challenge_method: String,
    pub me: Option<Url>,
    pub scope: Scopes,
    pub created_at: std::time::Instant,
    pub expires_at: std::time::Instant,
}

/// Access token data stored by the server
#[allow(dead_code)]
#[derive(Clone, Debug)]
pub struct AccessToken {
    pub token: String,
    pub me: Url,
    pub scope: Scopes,
    pub client_id: ClientId,
    pub issued_at: std::time::Instant,
    pub expires_at: std::time::Instant,
    /// OAuth 2.0 refresh token for obtaining new access tokens
    pub refresh_token: Option<String>,
    /// When the refresh token expires (typically longer than access token)
    pub refresh_token_expires_at: Option<std::time::Instant>,
}

/// Claims for JWT tokens
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct TokenClaims {
    pub me: Url,
    pub scope: Scopes,
    pub client_id: ClientId,
    pub issued_at: u64,
    pub expires_at: u64,
}

impl AuthorizationCode {
    pub fn new(
        code: String,
        client_id: ClientId,
        redirect_uri: RedirectUri,
        code_challenge: String,
        code_challenge_method: String,
        me: Option<Url>,
        scope: Scopes,
        expires_in: std::time::Duration,
    ) -> Self {
        let now = std::time::Instant::now();
        Self {
            code,
            client_id,
            redirect_uri,
            code_challenge,
            code_challenge_method,
            me,
            scope,
            created_at: now,
            expires_at: now + expires_in,
        }
    }

    pub fn is_expired(&self) -> bool {
        std::time::Instant::now() >= self.expires_at
    }
}

impl AccessToken {
    pub fn new(
        token: String,
        me: Url,
        scope: Scopes,
        client_id: ClientId,
        expires_in: std::time::Duration,
    ) -> Self {
        let now = std::time::Instant::now();
        Self {
            token,
            me,
            scope,
            client_id,
            issued_at: now,
            expires_at: now + expires_in,
            refresh_token: None,
            refresh_token_expires_at: None,
        }
    }

    /// Create an access token with a refresh token
    pub fn with_refresh_token(
        token: String,
        me: Url,
        scope: Scopes,
        client_id: ClientId,
        expires_in: std::time::Duration,
        refresh_token: String,
        refresh_token_expires_in: std::time::Duration,
    ) -> Self {
        let now = std::time::Instant::now();
        Self {
            token,
            me,
            scope,
            client_id,
            issued_at: now,
            expires_at: now + expires_in,
            refresh_token: Some(refresh_token),
            refresh_token_expires_at: Some(now + refresh_token_expires_in),
        }
    }

    pub fn is_expired(&self) -> bool {
        std::time::Instant::now() >= self.expires_at
    }

    /// Check if the refresh token is expired (or missing)
    pub fn is_refresh_token_expired(&self) -> bool {
        self.refresh_token_expires_at
            .map(|expires_at| std::time::Instant::now() >= expires_at)
            .unwrap_or(true)
    }
}
