//! Hermetic coverage for the Task-1 OHLC source-open surface (#80/#81/#92): the
//! documented *file-level* `None` contract of the canonical openers, and the #81
//! re-export firewall (a real-data source builds from `aura-ingest` alone). Every
//! item here is reached through `aura_ingest` ONLY — this test never names the
//! external `data_server` crate, which is the firewall it pins.
//!
//! Unlike the gated GER40 tests (which skip without a local Pepperstone archive),
//! these run everywhere: they drive a `DataServer` over a guaranteed-EMPTY base
//! path, so no archived file overlaps any window and the openers take their
//! documented "no data source present" branch deterministically — same input,
//! same `None`, every run, on any machine.

use std::sync::Arc;

// Firewall (#81): the ENTIRE source-open stack is imported from `aura_ingest`.
// `DataServer` / `DEFAULT_DATA_PATH` are re-exports; if a future change drops
// the re-export, this `use` stops compiling — that is the test.
use aura_core::Timestamp;
use aura_ingest::{
    default_data_server, open_ohlc, DataServer, M1Field, M1FieldSource, DEFAULT_DATA_PATH,
};

/// A `DataServer` over a base path that is guaranteed not to exist, so its symbol
/// index is empty on every machine (data-server's directory scan returns `Err`
/// for an absent path and yields no symbols). Gives a deterministic "no archive"
/// server with no filesystem writes and no dependence on local data.
fn empty_server() -> Arc<DataServer> {
    // A path component that cannot exist as a real archive directory; uniqueness
    // is irrelevant (an absent path scans empty regardless), but keep it obviously
    // synthetic so an accidental match is impossible.
    let absent = std::env::temp_dir().join("aura-ingest-no-such-archive-DO-NOT-CREATE");
    Arc::new(DataServer::new(absent))
}

/// Property: `open_ohlc` returns the *file-level* `None` (the "no data source to
/// read from" branch) when no archived file overlaps the window — distinct from
/// the "overlaps a file but holds zero bars" case, which would return
/// `Some(empty)`. Pins the documented Option semantics of the canonical OHLC
/// bundle opener (#80/#92) so a future refactor cannot silently turn "no source"
/// into a panic or an empty `Some`.
#[test]
fn open_ohlc_yields_file_level_none_when_no_archive_overlaps() {
    let server = empty_server();
    let from = Timestamp(0);
    let to = Timestamp(1_000_000_000_000_000_000); // ~2001, well-formed epoch-ns window
    assert!(
        open_ohlc(&server, "GER40", from, to).is_none(),
        "open_ohlc over an empty archive must return the file-level None (no source present)",
    );
}

/// Property: `M1FieldSource::open_window` — the engine-side epoch-ns `Timestamp`
/// mirror of the ms-path `open` — threads its `Timestamp` bounds through the
/// seam-private ms<->ns crossing and propagates data-server's file-level `None`
/// for an absent symbol, without panicking on the ns->ms divide. Pins that the
/// Timestamp opener is a faithful, total mirror of `open` on the "no source"
/// branch (the C3 one-crossing seam, #80).
#[test]
fn open_window_propagates_file_level_none_for_absent_symbol() {
    let server = empty_server();
    let from = Timestamp(0);
    let to = Timestamp(1_000_000_000_000_000_000);
    assert!(
        M1FieldSource::open_window(&server, "GER40", Some(from), Some(to), M1Field::Close)
            .is_none(),
        "open_window over an empty archive must propagate the file-level None",
    );
    // An open-ended window (None bounds) takes the same no-source branch — the
    // ns->ms mapping is a no-op on None, so the openness is preserved.
    assert!(
        M1FieldSource::open_window(&server, "GER40", None, None, M1Field::Close).is_none(),
        "open-ended open_window over an empty archive must also propagate None",
    );
}

/// Property: the #81 source-open surface is reachable from `aura_ingest` alone —
/// `default_data_server()` constructs the canonical-path server and the
/// re-exported `DEFAULT_DATA_PATH` is the path it points at, with the external
/// `data_server` crate never named by a consumer. Observable: the convenience
/// constructor agrees with constructing `DataServer::new(DEFAULT_DATA_PATH)` by
/// hand on the symbol-presence query (both report the same absence of a synthetic
/// symbol that no real archive can hold), so the convenience is a faithful alias.
#[test]
fn default_data_server_builds_from_ingest_alone_over_canonical_path() {
    let convenience = default_data_server();
    // The re-export is reachable and is what the convenience constructor uses.
    let by_hand = Arc::new(DataServer::new(DEFAULT_DATA_PATH));
    // A symbol no real Pepperstone archive holds: both servers agree it is absent,
    // independent of whether the local archive exists — so the convenience
    // constructor is a faithful alias for `DataServer::new(DEFAULT_DATA_PATH)`,
    // and the whole stack was built without naming `data_server`.
    const SYNTHETIC_ABSENT: &str = "AURA-INGEST-SYNTHETIC-NONEXISTENT-SYMBOL";
    assert_eq!(
        convenience.has_symbol(SYNTHETIC_ABSENT),
        by_hand.has_symbol(SYNTHETIC_ABSENT),
        "default_data_server() must agree with DataServer::new(DEFAULT_DATA_PATH)",
    );
    assert!(
        !convenience.has_symbol(SYNTHETIC_ABSENT),
        "a synthetic symbol no archive holds must be absent",
    );
}
