//! Account model for Dirigent identity management.

use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use crate::{secret::SecretSource, UserId};

#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum AccountKind {
    Local,
    Matrix,
}

#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct AccountProfile {
    #[serde(skip_serializing_if = "Option::is_none")]
    pub name: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub email: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub username: Option<String>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub display_name: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Account {
    #[serde(rename = "type")]
    pub kind: AccountKind,

    #[serde(skip)]
    pub config_name: String,

    #[serde(skip)]
    pub user_id: Option<UserId>,

    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
    pub credentials: HashMap<String, SecretSource>,

    #[serde(flatten)]
    pub profile: AccountProfile,

    #[serde(flatten)]
    pub properties: HashMap<String, serde_json::Value>,
}

impl Account {
    pub fn resolve_credential(&self, name: &str) -> Result<String, crate::SecretError> {
        self.credentials
            .get(name)
            .ok_or_else(|| crate::SecretError::EnvNotSet {
                key: format!("<credential '{}' not configured>", name),
            })
            .and_then(|s| s.resolve())
    }

    pub fn property_str(&self, key: &str) -> Option<&str> {
        self.properties.get(key).and_then(|v| v.as_str())
    }

    pub fn property_str_or<'a>(&'a self, key: &str, default: &'a str) -> &'a str {
        self.property_str(key).unwrap_or(default)
    }

    pub fn display_name(&self) -> &str {
        self.profile.display_name.as_deref()
            .or(self.profile.name.as_deref())
            .or(self.profile.username.as_deref())
            .unwrap_or(&self.config_name)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    fn sample_local() -> Account {
        Account {
            kind: AccountKind::Local,
            config_name: "local".to_string(),
            user_id: None,
            credentials: HashMap::new(),
            profile: AccountProfile { name: Some("Gabriel".to_string()), ..Default::default() },
            properties: HashMap::new(),
        }
    }

    fn sample_matrix() -> Account {
        let mut creds = HashMap::new();
        creds.insert("password".to_string(), SecretSource::Inline { value: "bot_pass".to_string() });
        let mut props = HashMap::new();
        props.insert("homeserver".to_string(), serde_json::json!("https://matrix.example.com"));
        props.insert("device_id".to_string(), serde_json::json!("DIRIGENT_01"));
        Account {
            kind: AccountKind::Matrix,
            config_name: "matrix-bot".to_string(),
            user_id: None,
            credentials: creds,
            profile: AccountProfile {
                username: Some("dirigent_bot".to_string()),
                display_name: Some("Dirigent Bot".to_string()),
                ..Default::default()
            },
            properties: props,
        }
    }

    #[test]
    fn test_local_display_name() {
        assert_eq!(sample_local().display_name(), "Gabriel");
    }

    #[test]
    fn test_matrix_display_name() {
        assert_eq!(sample_matrix().display_name(), "Dirigent Bot");
    }

    #[test]
    fn test_fallback_to_config_name() {
        let acct = Account {
            kind: AccountKind::Local,
            config_name: "fallback".to_string(),
            user_id: None,
            credentials: HashMap::new(),
            profile: AccountProfile::default(),
            properties: HashMap::new(),
        };
        assert_eq!(acct.display_name(), "fallback");
    }

    #[test]
    fn test_resolve_credential() {
        assert_eq!(sample_matrix().resolve_credential("password").unwrap(), "bot_pass");
    }

    #[test]
    fn test_missing_credential() {
        assert!(sample_local().resolve_credential("password").is_err());
    }

    #[test]
    fn test_property_str() {
        let acct = sample_matrix();
        assert_eq!(acct.property_str("homeserver"), Some("https://matrix.example.com"));
        assert_eq!(acct.property_str("nonexistent"), None);
    }

    #[test]
    fn test_property_str_or() {
        let acct = sample_matrix();
        assert_eq!(acct.property_str_or("device_id", "DEFAULT"), "DIRIGENT_01");
        assert_eq!(acct.property_str_or("missing", "DEFAULT"), "DEFAULT");
    }

    #[test]
    fn test_account_kind_serde() {
        let json = serde_json::to_string(&AccountKind::Matrix).unwrap();
        assert_eq!(json, r#""matrix""#);
        let back: AccountKind = serde_json::from_str(&json).unwrap();
        assert_eq!(back, AccountKind::Matrix);
    }

    #[test]
    fn test_account_serde_roundtrip() {
        let acct = sample_matrix();
        let json = serde_json::to_string(&acct).unwrap();
        let back: Account = serde_json::from_str(&json).unwrap();
        assert_eq!(back.kind, AccountKind::Matrix);
        assert_eq!(back.profile.display_name, Some("Dirigent Bot".to_string()));
        assert!(back.credentials.contains_key("password"));
        assert_eq!(back.property_str("homeserver"), Some("https://matrix.example.com"));
    }
}
