//! Shared helpers for provider integration tests (Gmail, Zoho).
//!
//! Credentials are loaded from either the `DAAKI_PROVIDER_CREDENTIALS` env var
//! (a JSON string) or a `credentials.json` file at the workspace root.
//!
//! Schema:
//! ```json
//! {
//!   "gmail": { "username": "user@gmail.com", "password": "app-password" },
//!   "zoho":  { "username": "user@zoho.com",  "password": "app-password" }
//! }
//! ```

#![allow(clippy::unwrap_used, clippy::expect_used, dead_code)]

use std::path::PathBuf;
use std::time::Duration;

use daaki_imap::{ImapConnection, TlsMode};
use std::sync::Once;

static TRACING_INIT: Once = Once::new();

/// Initialize the tracing subscriber for tests.
///
/// Controlled by `RUST_LOG` (e.g. `RUST_LOG=debug cargo test`).
/// Safe to call multiple times — only the first call takes effect.
pub fn init_tracing() {
    TRACING_INIT.call_once(|| {
        tracing_subscriber::fmt()
            .with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
            .with_test_writer()
            .try_init()
            .ok();
    });
}

/// Timeout for operations against real mail providers (slightly longer than
/// Docker tests to account for network latency).
pub const TIMEOUT: Duration = Duration::from_secs(30);

/// Default IMAP TLS port (RFC 8314 Section 3.3).
pub const IMAPS_PORT: u16 = 993;

/// Credentials for a single mail provider.
#[derive(Debug, Clone)]
pub struct ProviderCredentials {
    pub username: String,
    pub password: String,
}

/// All provider credentials loaded from config.
#[derive(Debug, Clone)]
pub struct Credentials {
    pub gmail: Option<ProviderCredentials>,
    pub zoho: Option<ProviderCredentials>,
}

/// Skip the current test if `$creds` is `None`, printing a message.
///
/// Usage:
/// ```ignore
/// let creds = helpers::load_credentials();
/// let gmail = skip_unless!(creds.gmail, "Gmail");
/// ```
macro_rules! skip_unless {
    ($opt:expr, $provider:expr) => {
        match &$opt {
            Some(c) => c,
            None => {
                eprintln!(
                    "skipping: {} credentials not found (set DAAKI_PROVIDER_CREDENTIALS or create credentials.json)",
                    $provider,
                );
                return;
            }
        }
    };
}
pub(crate) use skip_unless;

/// Load provider credentials from env var or file.
///
/// Tries `DAAKI_PROVIDER_CREDENTIALS` first (JSON string), then falls back to
/// `credentials.json` at the workspace root.
pub fn load_credentials() -> Credentials {
    // Try env var first.
    if let Ok(json) = std::env::var("DAAKI_PROVIDER_CREDENTIALS") {
        return parse_credentials(&json);
    }

    // Fall back to file. CARGO_MANIFEST_DIR points to the crate root
    // (crates/imap/), so we go up two levels to the workspace root.
    let mut path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    path.pop(); // crates/
    path.pop(); // workspace root
    path.push("credentials.json");

    match std::fs::read_to_string(&path) {
        Ok(json) => parse_credentials(&json),
        Err(_) => Credentials {
            gmail: None,
            zoho: None,
        },
    }
}

/// Parse the JSON credential blob into typed structs.
fn parse_credentials(json: &str) -> Credentials {
    let v: serde_json::Value = serde_json::from_str(json).expect("invalid credentials JSON");

    Credentials {
        gmail: parse_provider(&v, "gmail"),
        zoho: parse_provider(&v, "zoho"),
    }
}

/// Extract a single provider's credentials from the JSON value.
fn parse_provider(v: &serde_json::Value, key: &str) -> Option<ProviderCredentials> {
    let obj = v.get(key)?;
    let username = obj.get("username")?.as_str()?.to_owned();
    let password = obj.get("password")?.as_str()?.to_owned();
    Some(ProviderCredentials { username, password })
}

/// Connect to a provider's IMAP server with implicit TLS and LOGIN.
///
/// Uses the system CA roots (webpki-roots) — no custom TLS config needed
/// because real providers have valid certificates.
pub async fn connect_provider(host: &str, creds: &ProviderCredentials) -> ImapConnection {
    init_tracing();
    let conn = ImapConnection::connect(host, IMAPS_PORT, TlsMode::Implicit, TIMEOUT)
        .await
        .expect("failed to connect to provider IMAP server");
    conn.login(&creds.username, &creds.password, TIMEOUT)
        .await
        .expect("failed to LOGIN to provider");
    conn
}
