use serde::{Deserialize, Serialize};

/// Role assigned to an organization member.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum MemberRole {
    /// Full administrative access
    Admin,
    /// Standard user access
    Member,
    /// Read-only access
    Viewer,
}

impl MemberRole {
    /// Returns the display label for a member role.
    pub fn label(&self) -> &'static str {
        match self {
            Self::Admin => "Admin",
            Self::Member => "Member",
            Self::Viewer => "Viewer",
        }
    }

    /// Returns all available roles for populating dropdowns.
    pub fn all() -> &'static [Self] {
        &[Self::Admin, Self::Member, Self::Viewer]
    }
}

/// A member of the organization.
///
/// # Fields
///
/// * `id` - Unique member identifier
/// * `name` - Display name
/// * `email` - Email address
/// * `role` - Assigned role within the organization
/// * `joined_at` - ISO 8601 join date
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OrgMember {
    pub id: String,
    pub name: String,
    pub email: String,
    pub role: MemberRole,
    pub joined_at: String,
}

/// A pricing plan tier.
///
/// # Fields
///
/// * `id` - Unique plan identifier
/// * `name` - Plan display name (e.g. "Starter", "Team", "Enterprise")
/// * `price_eur` - Monthly price in euros
/// * `features` - List of included features
/// * `highlighted` - Whether this plan should be visually emphasized
/// * `max_seats` - Maximum number of seats, None for unlimited
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PricingPlan {
    pub id: String,
    pub name: String,
    pub price_eur: u32,
    pub features: Vec<String>,
    pub highlighted: bool,
    pub max_seats: Option<u32>,
}

/// Billing usage statistics for the current cycle.
///
/// # Fields
///
/// * `seats_used` - Number of active seats
/// * `seats_total` - Total seats in the plan
/// * `tokens_used` - Tokens consumed this billing cycle
/// * `tokens_limit` - Token limit for the billing cycle
/// * `billing_cycle_end` - ISO 8601 date when the current cycle ends
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct BillingUsage {
    pub seats_used: u32,
    pub seats_total: u32,
    pub tokens_used: u64,
    pub tokens_limit: u64,
    pub billing_cycle_end: String,
}

/// Aggregated token usage statistics from LiteLLM's spend tracking API.
///
/// # Fields
///
/// * `total_spend` - Total cost in USD across all models
/// * `total_prompt_tokens` - Sum of prompt (input) tokens
/// * `total_completion_tokens` - Sum of completion (output) tokens
/// * `total_tokens` - Sum of all tokens (prompt + completion)
/// * `model_breakdown` - Per-model usage breakdown
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct LitellmUsageStats {
    pub total_spend: f64,
    pub total_prompt_tokens: u64,
    pub total_completion_tokens: u64,
    pub total_tokens: u64,
    pub model_breakdown: Vec<ModelUsage>,
}

/// Token and spend usage for a single LLM model.
///
/// # Fields
///
/// * `model` - Model identifier (e.g. "gpt-4", "claude-3-opus")
/// * `spend` - Cost in USD for this model
/// * `prompt_tokens` - Prompt (input) tokens consumed
/// * `completion_tokens` - Completion (output) tokens generated
/// * `total_tokens` - Total tokens (prompt + completion)
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct ModelUsage {
    pub model: String,
    pub spend: f64,
    pub prompt_tokens: u64,
    pub completion_tokens: u64,
    pub total_tokens: u64,
}

/// Organisation-level settings stored in MongoDB.
///
/// These complement Keycloak's Organizations feature with
/// business-specific data (billing, feature flags).
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct OrgSettings {
    /// Keycloak organisation identifier.
    pub org_id: String,
    /// Active pricing plan identifier.
    pub plan_id: String,
    /// Feature flags toggled on for this organisation.
    pub enabled_features: Vec<String>,
    /// Stripe customer ID linked to this organisation.
    pub stripe_customer_id: String,
}

/// A single billing cycle record stored in MongoDB.
///
/// Captures seat and token usage between two dates for
/// invoicing and usage dashboards.
#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
pub struct OrgBillingRecord {
    /// Keycloak organisation identifier.
    pub org_id: String,
    /// ISO 8601 start of the billing cycle.
    pub cycle_start: String,
    /// ISO 8601 end of the billing cycle.
    pub cycle_end: String,
    /// Number of seats consumed during this cycle.
    pub seats_used: u32,
    /// Number of tokens consumed during this cycle.
    pub tokens_used: u64,
}

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

    #[test]
    fn member_role_label_admin() {
        assert_eq!(MemberRole::Admin.label(), "Admin");
    }

    #[test]
    fn member_role_label_member() {
        assert_eq!(MemberRole::Member.label(), "Member");
    }

    #[test]
    fn member_role_label_viewer() {
        assert_eq!(MemberRole::Viewer.label(), "Viewer");
    }

    #[test]
    fn member_role_all_returns_three_in_order() {
        let all = MemberRole::all();
        assert_eq!(all.len(), 3);
        assert_eq!(all[0], MemberRole::Admin);
        assert_eq!(all[1], MemberRole::Member);
        assert_eq!(all[2], MemberRole::Viewer);
    }

    #[test]
    fn member_role_serde_round_trip() {
        for role in MemberRole::all() {
            let json =
                serde_json::to_string(role).unwrap_or_else(|_| panic!("serialize {:?}", role));
            let back: MemberRole =
                serde_json::from_str(&json).unwrap_or_else(|_| panic!("deserialize {:?}", role));
            assert_eq!(*role, back);
        }
    }

    #[test]
    fn org_member_serde_round_trip() {
        let member = OrgMember {
            id: "m1".into(),
            name: "Alice".into(),
            email: "alice@example.com".into(),
            role: MemberRole::Admin,
            joined_at: "2025-01-01T00:00:00Z".into(),
        };
        let json = serde_json::to_string(&member).expect("serialize OrgMember");
        let back: OrgMember = serde_json::from_str(&json).expect("deserialize OrgMember");
        assert_eq!(member, back);
    }

    #[test]
    fn pricing_plan_with_max_seats() {
        let plan = PricingPlan {
            id: "team".into(),
            name: "Team".into(),
            price_eur: 49,
            features: vec!["SSO".into(), "Priority".into()],
            highlighted: true,
            max_seats: Some(25),
        };
        let json = serde_json::to_string(&plan).expect("serialize PricingPlan");
        let back: PricingPlan = serde_json::from_str(&json).expect("deserialize PricingPlan");
        assert_eq!(plan, back);
    }

    #[test]
    fn pricing_plan_without_max_seats() {
        let plan = PricingPlan {
            id: "enterprise".into(),
            name: "Enterprise".into(),
            price_eur: 199,
            features: vec!["Unlimited".into()],
            highlighted: false,
            max_seats: None,
        };
        let json = serde_json::to_string(&plan).expect("serialize PricingPlan");
        let back: PricingPlan = serde_json::from_str(&json).expect("deserialize PricingPlan");
        assert_eq!(plan, back);
        assert!(json.contains("null") || !json.contains("max_seats"));
    }

    #[test]
    fn billing_usage_serde_round_trip() {
        let usage = BillingUsage {
            seats_used: 5,
            seats_total: 10,
            tokens_used: 1_000_000,
            tokens_limit: 5_000_000,
            billing_cycle_end: "2025-12-31".into(),
        };
        let json = serde_json::to_string(&usage).expect("serialize BillingUsage");
        let back: BillingUsage = serde_json::from_str(&json).expect("deserialize BillingUsage");
        assert_eq!(usage, back);
    }

    #[test]
    fn org_settings_default() {
        let settings = OrgSettings::default();
        assert_eq!(settings.org_id, "");
        assert_eq!(settings.plan_id, "");
        assert!(settings.enabled_features.is_empty());
        assert_eq!(settings.stripe_customer_id, "");
    }

    #[test]
    fn org_billing_record_default() {
        let record = OrgBillingRecord::default();
        assert_eq!(record.org_id, "");
        assert_eq!(record.cycle_start, "");
        assert_eq!(record.cycle_end, "");
        assert_eq!(record.seats_used, 0);
        assert_eq!(record.tokens_used, 0);
    }

    #[test]
    fn litellm_usage_stats_default() {
        let stats = LitellmUsageStats::default();
        assert_eq!(stats.total_spend, 0.0);
        assert_eq!(stats.total_prompt_tokens, 0);
        assert_eq!(stats.total_completion_tokens, 0);
        assert_eq!(stats.total_tokens, 0);
        assert!(stats.model_breakdown.is_empty());
    }

    #[test]
    fn litellm_usage_stats_serde_round_trip() {
        let stats = LitellmUsageStats {
            total_spend: 12.34,
            total_prompt_tokens: 50_000,
            total_completion_tokens: 25_000,
            total_tokens: 75_000,
            model_breakdown: vec![
                ModelUsage {
                    model: "gpt-4".into(),
                    spend: 10.0,
                    prompt_tokens: 40_000,
                    completion_tokens: 20_000,
                    total_tokens: 60_000,
                },
                ModelUsage {
                    model: "claude-3-opus".into(),
                    spend: 2.34,
                    prompt_tokens: 10_000,
                    completion_tokens: 5_000,
                    total_tokens: 15_000,
                },
            ],
        };
        let json = serde_json::to_string(&stats).expect("serialize LitellmUsageStats");
        let back: LitellmUsageStats =
            serde_json::from_str(&json).expect("deserialize LitellmUsageStats");
        assert_eq!(stats, back);
    }

    #[test]
    fn model_usage_default() {
        let usage = ModelUsage::default();
        assert_eq!(usage.model, "");
        assert_eq!(usage.spend, 0.0);
        assert_eq!(usage.prompt_tokens, 0);
        assert_eq!(usage.completion_tokens, 0);
        assert_eq!(usage.total_tokens, 0);
    }

    #[test]
    fn model_usage_serde_round_trip() {
        let usage = ModelUsage {
            model: "gpt-4-turbo".into(),
            spend: 5.67,
            prompt_tokens: 30_000,
            completion_tokens: 15_000,
            total_tokens: 45_000,
        };
        let json = serde_json::to_string(&usage).expect("serialize ModelUsage");
        let back: ModelUsage = serde_json::from_str(&json).expect("deserialize ModelUsage");
        assert_eq!(usage, back);
    }

    #[test]
    fn litellm_usage_stats_empty_breakdown_round_trip() {
        let stats = LitellmUsageStats {
            total_spend: 0.0,
            total_prompt_tokens: 0,
            total_completion_tokens: 0,
            total_tokens: 0,
            model_breakdown: Vec::new(),
        };
        let json = serde_json::to_string(&stats).expect("serialize empty stats");
        let back: LitellmUsageStats = serde_json::from_str(&json).expect("deserialize empty stats");
        assert_eq!(stats, back);
    }
}
