use anyhow::Result;
use iam_client::client::IamClientConfig;
use iam_client::IamClient;
use iam_types::{PolicyBinding, Principal, PrincipalRef, Scope};
use tonic::metadata::MetadataValue;
use tonic::Request;

pub fn authorized_request<T>(message: T, token: &str) -> Request<T> {
    let mut req = Request::new(message);
    let header = format!("Bearer {}", token);
    let value = MetadataValue::try_from(header.as_str()).expect("valid bearer token metadata");
    req.metadata_mut().insert("authorization", value);
    req
}

pub async fn issue_controller_token(
    iam_server_addr: &str,
    principal_id: &str,
    org_id: &str,
    project_id: &str,
) -> Result<String> {
    let mut config = IamClientConfig::new(iam_server_addr).with_timeout(5000);
    if iam_server_addr.starts_with("http://") || !iam_server_addr.starts_with("https://") {
        config = config.without_tls();
    }

    let client = IamClient::connect(config).await?;
    let principal_ref = PrincipalRef::service_account(principal_id);
    let principal = match client.get_principal(&principal_ref).await? {
        Some(existing) => existing,
        None => {
            client
                .create_service_account(principal_id, principal_id, org_id, project_id)
                .await?
        }
    };

    ensure_project_admin_binding(&client, &principal, org_id, project_id).await?;

    let scope = Scope::project(project_id, org_id);
    client
        .issue_token(
            &principal,
            vec!["roles/ProjectAdmin".to_string()],
            scope,
            3600,
        )
        .await
        .map_err(Into::into)
}

async fn ensure_project_admin_binding(
    client: &IamClient,
    principal: &Principal,
    org_id: &str,
    project_id: &str,
) -> Result<()> {
    let scope = Scope::project(project_id, org_id);
    let bindings = client
        .list_bindings_for_principal(&principal.to_ref())
        .await?;

    let already_bound = bindings
        .iter()
        .any(|binding| binding.role_ref == "roles/ProjectAdmin" && binding.scope == scope);
    if already_bound {
        return Ok(());
    }

    let binding = PolicyBinding::new(
        format!("{}-project-admin-{}-{}", principal.id, org_id, project_id),
        principal.to_ref(),
        "roles/ProjectAdmin",
        scope,
    )
    .with_created_by("fleet-scheduler");
    client.create_binding(&binding).await?;
    Ok(())
}
