//! Operator-supplied provider config. Reads a TOML file shaped like:
//!
//! ```toml
//! [[providers]]
//! name           = "google"
//! display_name   = "Google"
//! issuer_url     = "https://accounts.google.com"
//! client_id      = "<google client id>"
//! client_secret  = "<google client secret>"
//! scopes         = "openid email profile"
//! ```
//!
//! Each entry is upserted into the `oidc_providers` table at startup.
//! `redirect_url` is computed from `--public-base-url` + `/oidc/callback`.
//!
//! TOML parsing uses the existing `rust-ini` crate? — no, we'd need a TOML
//! parser. We already have `toml` transitively via several deps; pull it in
//! directly for clarity.

use crate::database::{Database, OidcProviderRow};
use hbb_common::log;
use serde::Deserialize;
use std::path::Path;

#[derive(Debug, Deserialize)]
struct ProvidersFile {
    #[serde(default)]
    providers: Vec<ProviderEntry>,
}

#[derive(Debug, Deserialize)]
struct ProviderEntry {
    name: String,
    #[serde(default)]
    display_name: Option<String>,
    #[serde(default)]
    icon_url: Option<String>,
    issuer_url: String,
    client_id: String,
    client_secret: String,
    #[serde(default = "default_scopes")]
    scopes: String,
    /// Optional override; defaults to `<public-base-url>/oidc/callback`.
    #[serde(default)]
    redirect_url: Option<String>,
    #[serde(default = "default_true")]
    enabled: bool,
    /// Role-based admin sync. Set both to drive `is_admin` from the IdP:
    ///   admin_role  = "admin"
    ///   roles_claim = "urn:zitadel:iam:org:project:roles"   # Zitadel
    /// Or for a generic IdP that emits `roles: ["admin","user"]`:
    ///   admin_role  = "admin"
    ///   # roles_claim defaults to "roles"
    #[serde(default)]
    admin_role: Option<String>,
    #[serde(default)]
    roles_claim: Option<String>,
}

fn default_scopes() -> String {
    "openid email profile".to_string()
}
fn default_true() -> bool {
    true
}

pub async fn load_from_file(
    db: &Database,
    path: &Path,
    public_base_url: &str,
) -> Result<usize, String> {
    let bytes = std::fs::read_to_string(path)
        .map_err(|e| format!("read {}: {}", path.display(), e))?;
    let parsed: ProvidersFile =
        toml::from_str(&bytes).map_err(|e| format!("parse {}: {}", path.display(), e))?;
    let mut count = 0;
    for p in parsed.providers {
        let redirect_url = p
            .redirect_url
            .clone()
            .filter(|s| !s.is_empty())
            .unwrap_or_else(|| {
                let base = public_base_url.trim_end_matches('/');
                format!("{}/oidc/callback", base)
            });
        let row = OidcProviderRow {
            name: p.name.clone(),
            display_name: p.display_name,
            icon_url: p.icon_url,
            issuer_url: p.issuer_url,
            client_id: p.client_id,
            client_secret: p.client_secret,
            scopes: p.scopes,
            redirect_url,
            enabled: p.enabled,
            admin_role: p.admin_role.filter(|s| !s.is_empty()),
            roles_claim: p.roles_claim.filter(|s| !s.is_empty()),
        };
        db.oidc_provider_upsert(&row)
            .await
            .map_err(|e| format!("upsert {}: {}", p.name, e))?;
        count += 1;
        log::info!("oidc: provider {:?} configured", p.name);
    }
    Ok(count)
}
