use std::collections::HashMap;
use std::sync::RwLock;
use url::Url;

use crate::server::{AccessToken, AuthorizationCode, PendingAuthorization};

/// Server state containing all in-memory storage for the IndieAuth server
pub struct AppState {
    /// Signing key for JWT tokens (32 bytes)
    pub signing_key: [u8; 32],

    /// Server issuer URL (e.g., http://127.0.0.1:8080)
    pub issuer: Url,

    /// Pending authorization requests awaiting consent (id -> pending auth)
    pub pending_auths: RwLock<HashMap<String, PendingAuthorization>>,

    /// Active authorization codes (code -> code data)
    pub codes: RwLock<HashMap<String, AuthorizationCode>>,

    /// Active access tokens (token -> token data)
    pub tokens: RwLock<HashMap<String, AccessToken>>,

    /// Refresh token to access token mapping (refresh_token -> access_token)
    /// Used for looking up tokens when refreshing
    pub refresh_tokens: RwLock<HashMap<String, String>>,

    /// Demo users database
    pub users: HashMap<String, User>,
}

/// User account information
#[derive(Clone)]
pub struct User {
    pub password: String,
    pub profile: Profile,
}

/// User profile information that can be returned via scopes
#[derive(Clone)]
pub struct Profile {
    /// The identity URL (can be external or local)
    pub me: Url,
    /// Display name
    pub name: String,
    /// Profile image URL (optional)
    pub photo: Option<Url>,
    /// Email address (optional)
    pub email: Option<String>,
    /// User's main website URL (optional, may differ from me)
    pub url: Option<Url>,
}

impl AppState {
    /// Create a new AppState with the given signing key and issuer URL
    pub fn new(signing_key: [u8; 32], issuer: Url) -> Self {
        let users = Self::create_demo_users(&issuer);

        Self {
            signing_key,
            issuer,
            pending_auths: RwLock::new(HashMap::new()),
            codes: RwLock::new(HashMap::new()),
            tokens: RwLock::new(HashMap::new()),
            refresh_tokens: RwLock::new(HashMap::new()),
            users,
        }
    }

    /// Create demo users with different profile configurations
    fn create_demo_users(issuer: &Url) -> HashMap<String, User> {
        let mut users = HashMap::new();

        // User 1: Alice - Basic profile (name only)
        users.insert(
            "alice".to_string(),
            User {
                password: "alice".to_string(),
                profile: Profile {
                    me: issuer.join("user/alice").unwrap(),
                    name: "Alice".to_string(),
                    photo: None,
                    email: None,
                    url: None,
                },
            },
        );

        // User 2: Bob - Full profile with avatar image
        users.insert(
            "bob".to_string(),
            User {
                password: "bob".to_string(),
                profile: Profile {
                    me: issuer.join("user/bob").unwrap(),
                    name: "Bob Builder".to_string(),
                    photo: Some(
                        format!("https://api.dicebear.com/7.x/avataaars/svg?seed=bob")
                            .parse()
                            .unwrap(),
                    ),
                    email: Some("bob@example.com".to_string()),
                    url: None,
                },
            },
        );

        // User 3: Charlie - External URL profile with identicon
        users.insert(
            "charlie".to_string(),
            User {
                password: "charlie".to_string(),
                profile: Profile {
                    me: "https://charlie.example.com/".parse().unwrap(), // External identity!
                    name: "Charlie Web".to_string(),
                    photo: Some(
                        format!("https://api.dicebear.com/7.x/identicon/svg?seed=charlie")
                            .parse()
                            .unwrap(),
                    ),
                    email: Some("charlie@charlie.example.com".to_string()),
                    url: Some("https://charlie.example.com/".parse().unwrap()),
                },
            },
        );

        // User 4: Demo - Simple profile with bot avatar
        users.insert(
            "demo".to_string(),
            User {
                password: "demo".to_string(),
                profile: Profile {
                    me: issuer.join("user/demo").unwrap(),
                    name: "Demo User".to_string(),
                    photo: Some(
                        format!("https://api.dicebear.com/7.x/bottts/svg?seed=demo")
                            .parse()
                            .unwrap(),
                    ),
                    email: Some("demo@example.com".to_string()),
                    url: None,
                },
            },
        );

        users
    }

    /// Validate user credentials
    pub fn validate_user(&self, username: &str, password: &str) -> Option<&User> {
        self.users
            .get(username)
            .filter(|user| user.password == password)
    }

    /// Store a pending authorization request
    pub fn store_pending_auth(&self, id: String, auth_data: PendingAuthorization) {
        self.pending_auths.write().unwrap().insert(id, auth_data);
    }

    /// Retrieve and consume a pending authorization request (removes it from storage)
    pub fn consume_pending_auth(&self, id: &str) -> Option<PendingAuthorization> {
        self.pending_auths.write().unwrap().remove(id)
    }

    /// Store an authorization code
    pub fn store_code(&self, code: String, code_data: AuthorizationCode) {
        self.codes.write().unwrap().insert(code, code_data);
    }

    /// Retrieve and consume an authorization code (removes it from storage)
    pub fn consume_code(&self, code: &str) -> Option<AuthorizationCode> {
        self.codes.write().unwrap().remove(code)
    }

    /// Store an access token
    pub fn store_token(&self, token: String, token_data: AccessToken) {
        self.tokens.write().unwrap().insert(token, token_data);
    }

    /// Retrieve an access token (does not consume it)
    pub fn get_token(&self, token: &str) -> Option<AccessToken> {
        self.tokens.read().unwrap().get(token).cloned()
    }

    /// Store a refresh token mapping to an access token
    pub fn store_refresh_token(&self, refresh_token: String, access_token: String) {
        self.refresh_tokens
            .write()
            .unwrap()
            .insert(refresh_token, access_token);
    }

    /// Look up an access token by its refresh token
    /// Returns the access token string (not the full token data)
    pub fn get_access_token_by_refresh_token(&self, refresh_token: &str) -> Option<String> {
        self.refresh_tokens
            .read()
            .unwrap()
            .get(refresh_token)
            .cloned()
    }

    /// Remove a refresh token (used when revoking or after use)
    pub fn revoke_refresh_token(&self, refresh_token: &str) -> bool {
        self.refresh_tokens
            .write()
            .unwrap()
            .remove(refresh_token)
            .is_some()
    }

    /// Remove an access token and its associated refresh token
    pub fn revoke_token(&self, token: &str) -> bool {
        let mut tokens = self.tokens.write().unwrap();
        if let Some(token_data) = tokens.remove(token) {
            // Also remove the associated refresh token mapping
            if let Some(refresh_token) = &token_data.refresh_token {
                self.refresh_tokens.write().unwrap().remove(refresh_token);
            }
            true
        } else {
            false
        }
    }

    /// Remove expired authorization codes and access tokens
    ///
    /// This should be called periodically to prevent memory leaks from
    /// accumulated expired codes and tokens.
    pub fn cleanup_expired(&self) {
        let now = std::time::Instant::now();

        // Clean up expired pending authorizations
        {
            let mut pending = self.pending_auths.write().unwrap();
            pending.retain(|_, auth| now < auth.expires_at);
        }

        // Clean up expired authorization codes
        {
            let mut codes = self.codes.write().unwrap();
            codes.retain(|_, code| now < code.expires_at);
        }

        // Clean up expired access tokens and their refresh tokens
        {
            let mut tokens = self.tokens.write().unwrap();
            let mut refresh_tokens = self.refresh_tokens.write().unwrap();

            // Collect refresh tokens to remove
            let refresh_tokens_to_remove: Vec<String> = tokens
                .iter()
                .filter(|(_, token)| {
                    // Remove if access token expired OR refresh token expired
                    now >= token.expires_at || token.is_refresh_token_expired()
                })
                .filter_map(|(_, token)| token.refresh_token.clone())
                .collect();

            // Remove expired tokens
            tokens.retain(|_, token| now < token.expires_at);

            // Remove associated refresh tokens
            for rt in refresh_tokens_to_remove {
                refresh_tokens.remove(&rt);
            }
        }
    }
}
