use crate::common::errors::DomainError;
use crate::domain::entities::app_password::AppPassword;
use crate::domain::entities::device_code::DeviceCode;
use crate::domain::entities::session::Session;
use crate::domain::entities::user::User;
use uuid::Uuid;

// ============================================================================
// Cryptography Ports - Extracted from Domain to maintain Clean Architecture
// ============================================================================

/// Port for password hashing operations.
///
/// This trait abstracts cryptographic password operations, allowing the domain
/// layer to remain independent of specific hashing implementations (argon2, bcrypt, etc.)
///
/// Methods are async because implementations (e.g. Argon2) are CPU-intensive
/// and must run on a blocking thread pool to avoid starving Tokio workers.
pub trait PasswordHasherPort: Send + Sync + 'static {
    /// Hash a plain text password
    async fn hash_password(&self, password: &str) -> Result<String, DomainError>;

    /// Verify a plain text password against a hash
    async fn verify_password(&self, password: &str, hash: &str) -> Result<bool, DomainError>;
}

/// Claims contained in a JWT token
#[derive(Debug, Clone)]
pub struct TokenClaims {
    /// Subject identifier (user ID)
    pub sub: String,
    /// Expiration timestamp (seconds since Unix epoch)
    pub exp: i64,
    /// Issued at timestamp (seconds since Unix epoch)
    pub iat: i64,
    /// JWT unique ID
    pub jti: String,
    /// Username
    pub username: String,
    /// User email
    pub email: String,
    /// User role
    pub role: String,
}

/// Port for JWT token operations.
///
/// This trait abstracts token generation and validation, allowing the domain
/// layer to remain independent of specific JWT implementations.
pub trait TokenServicePort: Send + Sync + 'static {
    /// Generate an access token for a user
    fn generate_access_token(&self, user: &User) -> Result<String, DomainError>;

    /// Validate a token and extract its claims
    fn validate_token(&self, token: &str) -> Result<TokenClaims, DomainError>;

    /// Generate a refresh token
    fn generate_refresh_token(&self) -> String;

    /// Get refresh token expiry in seconds
    fn refresh_token_expiry_secs(&self) -> i64;

    /// Get refresh token expiry in days
    fn refresh_token_expiry_days(&self) -> i64;
}

// ============================================================================
// Storage Ports
// ============================================================================

pub trait UserStoragePort: Send + Sync + 'static {
    /// Creates a new user
    async fn create_user(&self, user: User) -> Result<User, DomainError>;

    /// Gets a user by ID
    async fn get_user_by_id(&self, id: Uuid) -> Result<User, DomainError>;

    /// Gets a user by username
    async fn get_user_by_username(&self, username: &str) -> Result<User, DomainError>;

    /// Gets a user by email
    async fn get_user_by_email(&self, email: &str) -> Result<User, DomainError>;

    /// Updates an existing user
    async fn update_user(&self, user: User) -> Result<User, DomainError>;

    /// Updates only the storage usage of a user
    async fn update_storage_usage(
        &self,
        user_id: Uuid,
        usage_bytes: i64,
    ) -> Result<(), DomainError>;

    /// Lists users with pagination
    async fn list_users(&self, limit: i64, offset: i64) -> Result<Vec<User>, DomainError>;

    /// Searches users by username or email (SQL ILIKE) with a limit.
    async fn search_users(&self, query: &str, limit: i64) -> Result<Vec<User>, DomainError>;

    /// Lists users by role (e.g., "admin" or "user")
    async fn list_users_by_role(&self, role: &str) -> Result<Vec<User>, DomainError>;

    /// Deletes a user by their ID
    async fn delete_user(&self, user_id: Uuid) -> Result<(), DomainError>;

    /// Changes a user's password
    async fn change_password(&self, user_id: Uuid, password_hash: &str) -> Result<(), DomainError>;

    /// Finds a user by OIDC provider + subject pair
    async fn get_user_by_oidc_subject(
        &self,
        provider: &str,
        subject: &str,
    ) -> Result<User, DomainError>;

    /// Activates or deactivates a user
    async fn set_user_active_status(&self, user_id: Uuid, active: bool) -> Result<(), DomainError>;

    /// Changes a user's role
    async fn change_role(&self, user_id: Uuid, role: &str) -> Result<(), DomainError>;

    /// Updates a user's storage quota
    async fn update_storage_quota(
        &self,
        user_id: Uuid,
        quota_bytes: i64,
    ) -> Result<(), DomainError>;

    /// Counts the total number of users
    async fn count_users(&self) -> Result<i64, DomainError>;
}

// ============================================================================
// OIDC Port
// ============================================================================

/// Represents the token set returned by the OIDC provider after code exchange
#[derive(Debug, Clone)]
pub struct OidcTokenSet {
    pub access_token: String,
    pub id_token: String,
    pub refresh_token: Option<String>,
}

/// Claims extracted from the validated OIDC ID token
#[derive(Debug, Clone)]
pub struct OidcIdClaims {
    pub sub: String,
    pub email: Option<String>,
    pub email_verified: Option<bool>,
    pub preferred_username: Option<String>,
    pub name: Option<String>,
    pub groups: Vec<String>,
    pub picture: Option<String>,
}

/// Port for OIDC operations — implemented in infrastructure layer
pub trait OidcServicePort: Send + Sync + 'static {
    /// Get the authorization URL for redirecting the user to the IdP.
    /// Includes PKCE code_challenge (S256) and nonce for ID token binding.
    /// This is async because it may need to fetch the OIDC discovery document.
    async fn get_authorize_url(
        &self,
        state: &str,
        nonce: &str,
        pkce_challenge: &str,
    ) -> Result<String, DomainError>;

    /// Exchange an authorization code for tokens, providing PKCE code_verifier.
    async fn exchange_code(
        &self,
        code: &str,
        pkce_verifier: &str,
    ) -> Result<OidcTokenSet, DomainError>;

    /// Validate an ID token and extract claims.
    /// If `expected_nonce` is provided, verifies the `nonce` claim matches.
    async fn validate_id_token(
        &self,
        id_token: &str,
        expected_nonce: Option<&str>,
    ) -> Result<OidcIdClaims, DomainError>;

    /// Fetch user info from the UserInfo endpoint (fallback for missing ID token claims)
    async fn fetch_user_info(&self, access_token: &str) -> Result<OidcIdClaims, DomainError>;

    /// Get the OIDC provider display name
    fn provider_name(&self) -> &str;
}

pub trait SessionStoragePort: Send + Sync + 'static {
    /// Creates a new session
    async fn create_session(&self, session: Session) -> Result<Session, DomainError>;

    /// Gets a session by refresh token
    async fn get_session_by_refresh_token(
        &self,
        refresh_token: &str,
    ) -> Result<Session, DomainError>;

    /// Revokes a specific session
    async fn revoke_session(&self, session_id: Uuid) -> Result<(), DomainError>;

    /// Revokes all sessions of a user
    async fn revoke_all_user_sessions(&self, user_id: Uuid) -> Result<u64, DomainError>;

    /// Revokes all sessions in a token family (used when replay of a revoked token is detected)
    async fn revoke_session_family(&self, family_id: Uuid) -> Result<u64, DomainError>;
}

// ============================================================================
// Device Authorization Grant Port (RFC 8628)
// ============================================================================

pub trait DeviceCodeStoragePort: Send + Sync + 'static {
    /// Persist a new device code flow
    async fn create_device_code(&self, device_code: DeviceCode) -> Result<DeviceCode, DomainError>;

    /// Find a device code by its opaque device_code token (used by client polling)
    async fn get_by_device_code(&self, device_code: &str) -> Result<DeviceCode, DomainError>;

    /// Find a pending device code by the short user_code (used on verification page)
    async fn get_pending_by_user_code(&self, user_code: &str) -> Result<DeviceCode, DomainError>;

    /// Update a device code (status change, token storage, poll timestamp, etc.)
    async fn update_device_code(&self, device_code: DeviceCode) -> Result<(), DomainError>;

    /// Delete expired device codes (cleanup job)
    async fn delete_expired(&self) -> Result<u64, DomainError>;

    /// List authorized device codes for a user (for UI management)
    async fn list_by_user(&self, user_id: Uuid) -> Result<Vec<DeviceCode>, DomainError>;

    /// Delete a specific device code by ID (revocation)
    async fn delete_by_id(&self, id: Uuid) -> Result<(), DomainError>;
}

// ============================================================================
// App Password Storage Port
// ============================================================================

/// Storage port for application-specific passwords (HTTP Basic Auth for DAV clients).
pub trait AppPasswordStoragePort: Send + Sync + 'static {
    /// Persist a new app password (hash already computed).
    async fn create(&self, app_password: AppPassword) -> Result<AppPassword, DomainError>;

    /// Get all active (non-expired) app passwords for a user.
    async fn list_by_user(&self, user_id: Uuid) -> Result<Vec<AppPassword>, DomainError>;

    /// Get a specific app password by ID.
    async fn get_by_id(&self, id: Uuid) -> Result<AppPassword, DomainError>;

    /// Get all active app passwords for a user ID (for Basic auth verification).
    /// This includes the password hash for verification.
    async fn get_active_by_user_id(&self, user_id: Uuid) -> Result<Vec<AppPassword>, DomainError>;

    /// Update the `last_used_at` timestamp after a successful authentication.
    async fn touch_last_used(&self, id: Uuid) -> Result<(), DomainError>;

    /// Get active app passwords for a user filtered by token prefix (first 8 chars).
    /// More efficient than `get_active_by_user_id` when the password prefix is known.
    async fn get_active_by_user_prefix(
        &self,
        user_id: Uuid,
        prefix: &str,
    ) -> Result<Vec<AppPassword>, DomainError>;

    /// Deactivate (soft-delete) an app password, scoped to the owning user.
    async fn revoke(&self, id: Uuid, user_id: Uuid) -> Result<(), DomainError>;

    /// Delete an app password owned by a specific user. Returns true if found and deleted.
    async fn delete_by_user_and_id(&self, id: Uuid, user_id: Uuid) -> Result<bool, DomainError>;

    /// Hard-delete expired/revoked app passwords (cleanup).
    async fn delete_expired(&self) -> Result<u64, DomainError>;
}
