//! v1.1.4 §10a: the original module backend error MUST be logged at
//! error level (so operators can still diagnose), even though it is
//! redacted from the script-visible error.
//!
//! This test owns the process-global tracing subscriber, so it lives in
//! its own integration-test binary (one `set_global_default` per
//! process). A unique sentinel in the backend error keeps the assertion
//! robust against any concurrently-running test's log output.

use std::collections::BTreeMap;
use std::io::Write;
use std::sync::{Arc, Mutex};

use async_trait::async_trait;
use picloud_executor_core::{Engine, ExecRequest, InvocationType, Limits};
use picloud_shared::{
    AppId, ExecutionId, ModuleScript, ModuleSource, ModuleSourceError, NoopDeadLetterService,
    NoopDocsService, NoopEventEmitter, NoopHttpService, NoopKvService, RequestId, ScriptId,
    ScriptOwner, ScriptSandbox, Services,
};
use serde_json::Value;
use tracing_subscriber::fmt::MakeWriter;

const SENTINEL: &str = "connection refused PICLOUD-SENTINEL-9f3a";

struct FailingSource;

#[async_trait]
impl ModuleSource for FailingSource {
    async fn resolve(
        &self,
        _origin: ScriptOwner,
        _name: &str,
    ) -> Result<Option<ModuleScript>, ModuleSourceError> {
        Err(ModuleSourceError::Backend(SENTINEL.to_string()))
    }
}

/// `MakeWriter` that appends to a shared buffer.
#[derive(Clone)]
struct SharedBuf(Arc<Mutex<Vec<u8>>>);

impl Write for SharedBuf {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        self.0.lock().unwrap().extend_from_slice(buf);
        Ok(buf.len())
    }
    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

impl<'a> MakeWriter<'a> for SharedBuf {
    type Writer = SharedBuf;
    fn make_writer(&'a self) -> Self::Writer {
        self.clone()
    }
}

fn req(app_id: AppId) -> ExecRequest {
    let execution_id = ExecutionId::new();
    ExecRequest {
        execution_id,
        request_id: RequestId::new(),
        script_id: ScriptId::new(),
        script_name: "redaction-test".into(),
        invocation_type: InvocationType::Http,
        path: "/x".into(),
        method: String::new(),
        headers: BTreeMap::new(),
        body: Value::Null,
        params: BTreeMap::new(),
        query: BTreeMap::new(),
        rest: String::new(),
        sandbox_overrides: ScriptSandbox::default(),
        app_id,
        script_owner: None,
        principal: None,
        trigger_depth: 0,
        root_execution_id: execution_id,
        is_dead_letter_handler: false,
        event: None,
    }
}

#[tokio::test(flavor = "multi_thread")]
async fn original_backend_error_is_logged_at_error_level() {
    let buf = Arc::new(Mutex::new(Vec::<u8>::new()));
    let subscriber = tracing_subscriber::fmt()
        .with_writer(SharedBuf(buf.clone()))
        .with_max_level(tracing::Level::ERROR)
        .with_ansi(false)
        .finish();
    tracing::subscriber::set_global_default(subscriber)
        .expect("this test owns the global subscriber for its binary");

    let services = Services::new(
        Arc::new(NoopKvService),
        Arc::new(NoopDocsService),
        Arc::new(NoopDeadLetterService),
        Arc::new(NoopEventEmitter),
        Arc::new(FailingSource),
        Arc::new(NoopHttpService),
        Arc::new(picloud_shared::NoopFilesService),
        Arc::new(picloud_shared::NoopPubsubService),
        Arc::new(picloud_shared::NoopSecretsService),
        Arc::new(picloud_shared::NoopEmailService),
        Arc::new(picloud_shared::NoopUsersService),
        Arc::new(picloud_shared::NoopQueueService),
        Arc::new(picloud_shared::NoopInvokeService),
        Arc::new(picloud_shared::NoopVarsService),
    );
    let engine = Engine::new(Limits::default(), services);

    let err = engine
        .execute(r#"import "x" as x; 1"#, req(AppId::new()))
        .expect_err("backend error should surface");

    // Script-visible: redacted.
    let msg = format!("{err:?}");
    assert!(msg.contains("module backend unavailable"), "got {msg}");
    assert!(
        !msg.contains("PICLOUD-SENTINEL"),
        "script error leaked the original: {msg}"
    );

    // Operator log: the original sentinel IS present, at ERROR level.
    let logged = String::from_utf8(buf.lock().unwrap().clone()).unwrap();
    assert!(
        logged.contains(SENTINEL),
        "original backend error should be logged; captured: {logged}"
    );
    assert!(
        logged.contains("ERROR"),
        "should be logged at error level; captured: {logged}"
    );
}
