/*-------------------------------------------------------------------------
 * Copyright (c) Microsoft Corporation.  All rights reserved.
 *
 * documentdb_gateway_core/src/testing/connection_context.rs
 *
 * Shared connection-context construction helpers for unit tests.
 *
 *-------------------------------------------------------------------------
 */

use std::sync::Arc;

use tokio::sync::OnceCell;
use uuid::Uuid;

use crate::{
    configuration::{
        CertInputType, CertificateOptions, DocumentDBSetupConfiguration, DynamicConfiguration,
        SetupConfiguration,
    },
    context::{ConnectionContext, ServiceContext},
    postgres::{
        conn_mgmt::{
            ConnectionPool, PgPoolSettings, PoolManager, AUTHENTICATION_MAX_CONNECTIONS,
            SYSTEM_REQUESTS_MAX_CONNECTIONS,
        },
        create_query_catalog,
    },
    service::TlsProvider,
    telemetry::{MetricsOptions, TelemetryOptions, TelemetryProvider},
};

pub async fn test_connection_context(
    metrics_enabled: bool,
    dynamic_configuration: Arc<dyn DynamicConfiguration>,
    telemetry_provider: Option<Box<dyn TelemetryProvider>>,
) -> ConnectionContext {
    let setup_configuration = test_setup_configuration(metrics_enabled);
    let query_catalog = create_query_catalog();
    let application_name = setup_configuration.application_name().to_owned();
    let postgres_system_user = setup_configuration.postgres_system_user.clone();

    let system_requests_pool = ConnectionPool::new_with_user(
        &setup_configuration,
        &query_catalog,
        &postgres_system_user,
        None,
        &format!("{application_name}-SystemRequests"),
        PgPoolSettings::system_pool_settings(SYSTEM_REQUESTS_MAX_CONNECTIONS),
    )
    .expect("system requests pool should initialize for tests");

    let authentication_pool = ConnectionPool::new_with_user(
        &setup_configuration,
        &query_catalog,
        &postgres_system_user,
        None,
        &format!("{application_name}-PreAuthRequests"),
        PgPoolSettings::system_pool_settings(AUTHENTICATION_MAX_CONNECTIONS),
    )
    .expect("authentication pool should initialize for tests");

    let pool_manager = Arc::new(PoolManager::new(
        query_catalog,
        Box::new(setup_configuration.clone()),
        system_requests_pool,
        authentication_pool,
    ));
    let service_context = ServiceContext::new(
        Box::new(setup_configuration),
        dynamic_configuration,
        pool_manager,
        test_tls_provider().await,
    );

    ConnectionContext::new(
        service_context,
        telemetry_provider,
        "10.0.0.1".to_owned(),
        None,
        Uuid::from_u128(1),
        "TCP".to_owned(),
    )
}

async fn test_tls_provider() -> TlsProvider {
    static TLS_PROVIDER: OnceCell<TlsProvider> = OnceCell::const_new();

    TLS_PROVIDER
        .get_or_init(|| async {
            // tls.rs::DEFAULT_TLS_STATE_DIR is now `/var/lib/documentdb-gateway/tls`
            // (absolute) so the auto-generated cert/key land in a documented,
            // operator-discoverable location at runtime instead of the
            // process's inherited cwd. That path is not writable in
            // unprivileged test environments (CI runners, dev workstations),
            // so the auto-gen path used to fall back to `./cert.pem` /
            // `./pkey.pem` is no longer correct. Point the test fixture at
            // a process-unique tempdir via the documented env override —
            // this exercises the same code path that operators use to
            // relocate certs and keeps tests hermetic / parallel-safe.
            //
            // Set unconditionally (not just when unset): the OnceCell
            // initializer only runs once per test process, and a stray
            // value inherited from the environment would otherwise
            // collide between concurrent `cargo test` invocations.
            let tls_state_dir =
                std::env::temp_dir().join(format!("documentdb-gateway-tls-{}", Uuid::new_v4()));
            std::fs::create_dir_all(&tls_state_dir)
                .expect("test TLS state dir should be creatable");
            // SAFETY: tests within a single process share env; the OnceCell
            // guarantees we set this exactly once before any TLS provider
            // construction. Concurrent test binaries get isolated tempdirs
            // because each spawned process generates its own UUID above.
            unsafe {
                std::env::set_var("DOCUMENTDB_TLS_STATE_DIR", tls_state_dir.as_os_str());
            }

            TlsProvider::new(
                &CertificateOptions {
                    cert_type: CertInputType::PemAutoGenerated,
                    ..Default::default()
                },
                None,
                None,
            )
            .await
            .expect("test TLS provider should initialize")
        })
        .await
        .clone()
}

fn test_setup_configuration(metrics_enabled: bool) -> DocumentDBSetupConfiguration {
    let system_user = std::env::var("PostgresSystemUser")
        .unwrap_or_else(|_| whoami::username().unwrap_or_default());

    DocumentDBSetupConfiguration {
        node_host_name: "localhost".to_owned(),
        blocked_role_prefixes: Vec::new(),
        gateway_listen_port: Some(10260),
        allow_transaction_snapshot: Some(false),
        certificate_options: CertificateOptions {
            cert_type: CertInputType::PemAutoGenerated,
            ..Default::default()
        },
        postgres_system_user: system_user.clone(),
        postgres_data_user: system_user,
        telemetry_options: Some(TelemetryOptions {
            metrics: Some(MetricsOptions {
                enabled: Some(metrics_enabled),
                ..Default::default()
            }),
            ..Default::default()
        }),
        ..Default::default()
    }
}
