// Contract tests for browser-backed upstream sync probes.
//
// Scope: statically guard the browser, mirrored, and local stimulus probe
// drivers used for client-to-server transport tuning.
// Targets: `scripts/manual/run_upstream_browser_av_sync.sh`,
// `scripts/manual/run_upstream_mirrored_av_sync.sh`,
// `scripts/manual/run_google_meet_observer_probe.sh`, and local stimulus code.
// Why: after server-to-RCT calibration, these scripts become the next tuning
// surface and must not drift back to synthetic or split upstream paths.

const BROWSER_SYNC_SCRIPT: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/scripts/manual/run_upstream_browser_av_sync.sh"
));
const MIRRORED_SYNC_SCRIPT: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/scripts/manual/run_upstream_mirrored_av_sync.sh"
));
const GOOGLE_MEET_OBSERVER_SCRIPT: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/scripts/manual/run_google_meet_observer_probe.sh"
));
const LOCAL_STIMULUS: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/scripts/manual/local_av_stimulus.py"
));
const SYNC_PROBE_RUNNER: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/client/src/sync_probe/runner.rs"
));
const SYNC_PROBE_BUNDLED_TRANSPORT: &str = include_str!(concat!(
    env!("CARGO_MANIFEST_DIR"),
    "/client/src/sync_probe/runner/bundled_transport.rs"
));

#[test]
fn browser_sync_script_can_delegate_to_a_real_path_driver() {
    for expected in [
        "BROWSER_RECORD_SECONDS=${BROWSER_RECORD_SECONDS:-${PROBE_DURATION_SECONDS}}",
        "BROWSER_SYNC_DRIVER_COMMAND=${BROWSER_SYNC_DRIVER_COMMAND:-}",
        "BROWSER_CONSUMER_REUSE_SESSION=${BROWSER_CONSUMER_REUSE_SESSION:-0}",
        "BROWSER_ANALYSIS_REQUIRED=${BROWSER_ANALYSIS_REQUIRED:-1}",
        "SYNC_ANALYZE_EVENT_WIDTH_CODES=${SYNC_ANALYZE_EVENT_WIDTH_CODES:-}",
        "==> running custom browser sync driver",
        "bash -lc \"${BROWSER_SYNC_DRIVER_COMMAND}\"",
        "browser_start_token=${browser_start_token}",
        "uploaded_start_token",
        "BROWSER_START_TOKEN",
        "analysis-failure.json",
        "BROWSER_ANALYSIS_REQUIRED=${BROWSER_ANALYSIS_REQUIRED}",
        "BROWSER_START_ATTEMPTS=${BROWSER_START_ATTEMPTS:-5}",
        "browser consumer start attempt ${attempt}/${BROWSER_START_ATTEMPTS} failed; retrying",
        "--event-width-codes",
        "--report-dir \"${LOCAL_REPORT_DIR}\"",
        "for attempt in 1 2 3 4 5",
        "capture fetch attempt ${attempt} failed; retrying",
        "failed to fetch browser capture from ${TETHYS_HOST}:${REMOTE_CAPTURE}",
        r"raw activity delta was ([+-]?[0-9]+(?:\.[0-9]+)?) ms ",
        r"\(video=([0-9]+(?:\.[0-9]+)?)s audio=([0-9]+(?:\.[0-9]+)?)s\)",
    ] {
        assert!(
            BROWSER_SYNC_SCRIPT.contains(expected),
            "browser sync script should contain {expected}"
        );
    }
    assert!(
        !BROWSER_SYNC_SCRIPT.contains(r"(?:\\.[0-9]+)?"),
        "browser sync raw-delta parser should not require a literal backslash before decimals"
    );
}

#[test]
fn sync_probe_runner_uses_bundled_webcam_media_path() {
    for expected in [
        "bundled_webcam_media",
        "refusing to measure split upstream",
        "UpstreamMediaBundle",
        "stream_webcam_media",
        "PROBE_BUNDLE_SESSION_ID",
        "PROBE_BUNDLE_AUDIO_GRACE",
        "server does not advertise bundled webcam media",
    ] {
        assert!(
            SYNC_PROBE_RUNNER.contains(expected) || SYNC_PROBE_BUNDLED_TRANSPORT.contains(expected),
            "sync probe runner should contain {expected}"
        );
    }
    for forbidden in [
        ".stream_camera(Request::new(outbound))",
        ".stream_microphone(Request::new(outbound))",
    ] {
        assert!(
            !SYNC_PROBE_RUNNER.contains(forbidden)
                && !SYNC_PROBE_BUNDLED_TRANSPORT.contains(forbidden),
            "sync probe runner must not use old split upstream RPC {forbidden}"
        );
    }
}

#[test]
fn mirrored_sync_script_uses_real_client_capture_path() {
    for expected in [
        "local_av_stimulus.py",
        "lesavka-client",
        "LESAVKA_HEADLESS=1",
        "LESAVKA_MEDIA_CONTROL=\"${MEDIA_CONTROL}\"",
        "LESAVKA_REQUIRE_EXPLICIT_MEDIA_SOURCES=\"${LESAVKA_REQUIRE_EXPLICIT_MEDIA_SOURCES:-1}\"",
        "--no-launcher --server \"${RESOLVED_LESAVKA_SERVER_ADDR}\"",
        "BROWSER_SYNC_DRIVER_COMMAND=\"${driver_command}\"",
        "SYNC_ANALYZE_EVENT_WIDTH_CODES=\"${PROBE_EVENT_WIDTH_CODES}\"",
        "run_upstream_browser_av_sync.sh",
        "wait_for_stimulus_page_ready 15",
        "Point the real webcam at the stimulus window",
        "==> Lesavka versions under test",
        "lesavka-relayctl",
        "--bin lesavka-relayctl",
        "client_revision=",
        "server_version=",
        "server_revision=",
        "combined version+revision",
        "run_status=0",
        "run_mirrored_segments || run_status=$?",
        "LESAVKA_SYNC_APPLY_CALIBRATION=${LESAVKA_SYNC_APPLY_CALIBRATION:-0}",
        "LESAVKA_SYNC_SAVE_CALIBRATION=${LESAVKA_SYNC_SAVE_CALIBRATION:-0}",
        "LESAVKA_SYNC_CALIBRATION_TARGET=${LESAVKA_SYNC_CALIBRATION_TARGET:-video}",
        "LESAVKA_SYNC_ADAPTIVE_CALIBRATION=${LESAVKA_SYNC_ADAPTIVE_CALIBRATION:-0}",
        "LESAVKA_SYNC_CALIBRATION_SEGMENTS=${LESAVKA_SYNC_CALIBRATION_SEGMENTS:-1}",
        "LESAVKA_SYNC_CONTINUOUS_BROWSER=${LESAVKA_SYNC_CONTINUOUS_BROWSER:-${LESAVKA_SYNC_ADAPTIVE_CALIBRATION}}",
        "LESAVKA_SYNC_CONTINUE_ON_ANALYSIS_FAILURE=${LESAVKA_SYNC_CONTINUE_ON_ANALYSIS_FAILURE:-${LESAVKA_SYNC_ADAPTIVE_CALIBRATION}}",
        "LESAVKA_SYNC_SEGMENT_SETTLE_SECONDS=${LESAVKA_SYNC_SEGMENT_SETTLE_SECONDS:-3}",
        "PROBE_AUDIO_GAIN=${PROBE_AUDIO_GAIN:-0.55}",
        "LESAVKA_STIMULUS_PREVIEW_SECONDS=${LESAVKA_STIMULUS_PREVIEW_SECONDS:-4}",
        "LESAVKA_OPEN_MANUAL_REVIEW_DOLPHIN=${LESAVKA_OPEN_MANUAL_REVIEW_DOLPHIN:-1}",
        "LESAVKA_SYNC_PROVISIONAL_CALIBRATION=${LESAVKA_SYNC_PROVISIONAL_CALIBRATION:-${LESAVKA_SYNC_ADAPTIVE_CALIBRATION}}",
        "LESAVKA_SYNC_PROVISIONAL_MIN_PAIRS=${LESAVKA_SYNC_PROVISIONAL_MIN_PAIRS:-3}",
        "LESAVKA_SYNC_PROVISIONAL_MAX_P95_MS=${LESAVKA_SYNC_PROVISIONAL_MAX_P95_MS:-350}",
        "LESAVKA_SYNC_PROVISIONAL_MAX_DRIFT_MS=${LESAVKA_SYNC_PROVISIONAL_MAX_DRIFT_MS:-250}",
        "LESAVKA_SYNC_PROVISIONAL_GAIN=${LESAVKA_SYNC_PROVISIONAL_GAIN:-0.5}",
        "LESAVKA_SYNC_PROVISIONAL_MAX_STEP_US=${LESAVKA_SYNC_PROVISIONAL_MAX_STEP_US:-150000}",
        "LESAVKA_SYNC_RAW_FAILURE_CALIBRATION=${LESAVKA_SYNC_RAW_FAILURE_CALIBRATION:-0}",
        "LESAVKA_SYNC_RAW_FAILURE_MIN_PAIRS=${LESAVKA_SYNC_RAW_FAILURE_MIN_PAIRS:-3}",
        "LESAVKA_SYNC_RAW_FAILURE_MAX_ABS_DELTA_MS=${LESAVKA_SYNC_RAW_FAILURE_MAX_ABS_DELTA_MS:-350}",
        "LESAVKA_SYNC_CONFIRM_AFTER_CALIBRATION=${LESAVKA_SYNC_CONFIRM_AFTER_CALIBRATION:-${LESAVKA_SYNC_ADAPTIVE_CALIBRATION}}",
        "LESAVKA_SYNC_CONFIRMATION_SEGMENTS=${LESAVKA_SYNC_CONFIRMATION_SEGMENTS:-1}",
        "LESAVKA_SYNC_REQUIRE_CONFIRMATION_PASS=${LESAVKA_SYNC_REQUIRE_CONFIRMATION_PASS:-${LESAVKA_SYNC_CONFIRM_AFTER_CALIBRATION}}",
        "LESAVKA_SYNC_CONFIRMATION_SEGMENTS must be a non-negative integer",
        "LESAVKA_SYNC_TOTAL_SEGMENTS=$((LESAVKA_SYNC_CALIBRATION_SEGMENTS + LESAVKA_SYNC_CONFIRMATION_SEGMENTS))",
        "export LESAVKA_SYNC_PROVISIONAL_CALIBRATION",
        "export LESAVKA_SYNC_RAW_FAILURE_CALIBRATION",
        "export LESAVKA_SYNC_RAW_FAILURE_MIN_PAIRS",
        "LESAVKA_SYNC_ADAPTIVE_CALIBRATION",
        "LESAVKA_SYNC_CALIBRATION_SEGMENTS=4",
        "browser_consumer_reuse_session=${reuse_browser_session}",
        "browser_analysis_required=${analysis_required}",
        "BROWSER_CONSUMER_REUSE_SESSION=\"${reuse_browser_session}\"",
        "BROWSER_ANALYSIS_REQUIRED=\"${analysis_required}\"",
        "--audio-gain \"${PROBE_AUDIO_GAIN}\"",
        "run_stimulus_preview",
        "write_stimulus_driver_script",
        "local_stimulus_started=true",
        "observed_start_token",
        "audio_state",
        "LESAVKA_SYNC_CALIBRATION_SEGMENTS must be a positive integer",
        "run_mirrored_segments",
        "summarize_adaptive_probe_metrics",
        "for segment in $(seq 1 \"${LESAVKA_SYNC_TOTAL_SEGMENTS}\")",
        "segment_phase",
        "confirmation segment: calibration apply disabled so this segment tests the active calibration",
        "segment-${segment}",
        "calibration-before.env",
        "planner-before.env",
        "calibration-decision.env",
        "segment-metrics.csv",
        "segment-metrics.jsonl",
        "segment-events.csv",
        "segment-events.jsonl",
        "manual-review",
        "manual_review_html",
        "manual_review_dir",
        "open_manual_review_in_dolphin",
        "dolphin",
        "capture_path",
        "confirmation-summary.json",
        "confirmation_passed",
        "check_confirmation_result",
        "confirmation check failed",
        "analysis_failure_reason",
        "probe_activity_start_delta_ms",
        "blind-targets.json",
        "no segment produced a passing probe verdict; refusing to invent blind targets",
        "confirmation did not pass; refusing to promote calibration-only segments to blind targets",
        "candidate_good_calibration_segments",
        "decision_mode",
        "decision_provisional_video_recommendation_us",
        "planner_live_lag_ms_after",
        "probe_p95_abs_skew_ms",
        "transport/server receive jitter",
        "settling ${LESAVKA_SYNC_SEGMENT_SETTLE_SECONDS}s before next segment",
        "print_upstream_calibration_state \"before mirrored run\"",
        "maybe_apply_probe_calibration",
        "calibration_ready=${calibration_ready}",
        "calibration_decision_mode=${calibration_decision_mode}",
        "bounded provisional correction from median skew",
        "bounded provisional correction from analyzer-failure raw activity",
        "raw_failure_calibration_enabled",
        "raw analyzer-failure calibration refused: ",
        "raw_failure_min_pairs",
        "provisional calibration not saved",
        "calibration apply refused: ${calibration_decision_note}",
        "calibrate \"${calibration_apply_audio_delta_us}\" \"${calibration_apply_video_delta_us}\"",
        "LESAVKA_UPSTREAM_BLIND_HEAL is server-side",
        "calibration-save-default",
        "print_upstream_sync_state \"after mirrored run\"",
        "print_upstream_calibration_state \"after mirrored run\"",
        "==> mirrored probe failed",
    ] {
        assert!(
            MIRRORED_SYNC_SCRIPT.contains(expected),
            "mirrored sync script should contain {expected}"
        );
    }
    assert!(
        !MIRRORED_SYNC_SCRIPT.contains("lesavka-sync-probe"),
        "mirrored sync must not use the synthetic direct sender"
    );
}

#[test]
fn google_meet_observer_probe_is_manual_and_uses_bundled_synthetic_media() {
    for expected in [
        "Google Meet observer probe checklist",
        "no Google credentials, sudo, or browser automation are used",
        "LESAVKA_GOOGLE_MEET_URL=${LESAVKA_GOOGLE_MEET_URL:-}",
        "LESAVKA_MEET_OBSERVER_CAPTURE=${LESAVKA_MEET_OBSERVER_CAPTURE:-}",
        "LESAVKA_MEET_SKIP_PROMPTS=${LESAVKA_MEET_SKIP_PROMPTS:-0}",
        "LESAVKA_MEET_START_DELAY_SECONDS=${LESAVKA_MEET_START_DELAY_SECONDS:-0}",
        "LESAVKA_MEET_START_DELAY_SECONDS must be a non-negative integer",
        "LESAVKA_MEET_ANALYSIS_REQUIRED=${LESAVKA_MEET_ANALYSIS_REQUIRED:-0}",
        "lesavka-sync-probe",
        "--event-width-codes \"${PROBE_EVENT_WIDTH_CODES}\"",
        "--timeline-json \"${CLIENT_TIMELINE_JSON}\"",
        "Join Meet on RCT, join as observer here, pin the RCT tile",
        "Stop/save the observer recording now",
        "Path to observer recording for analysis (blank to skip)",
        "lesavka-sync-analyze",
        "--report-dir \"${ANALYSIS_DIR}\"",
        "manual-timing.json",
        "observer-google-meet-capture",
        "operator-checklist.txt",
        "Observer capture times are operator-confirmed",
    ] {
        assert!(
            GOOGLE_MEET_OBSERVER_SCRIPT.contains(expected),
            "Google Meet observer probe should contain {expected}"
        );
    }
    for forbidden in [
        "google-chrome --remote-debugging",
        "chromedriver",
        "geckodriver",
        "sudo ",
        "PASSWORD",
        "VAULT",
        "vault",
        "read -s",
    ] {
        assert!(
            !GOOGLE_MEET_OBSERVER_SCRIPT.contains(forbidden),
            "Google Meet observer probe must stay manual/non-secret: {forbidden}"
        );
    }
}

#[test]
fn local_stimulus_matches_sync_analyzer_pulse_contract() {
    for expected in [
        "--warmup-seconds",
        "--pulse-period-ms",
        "--pulse-width-ms",
        "--marker-tick-period",
        "--audio-gain",
        "/preview",
        "preview_token",
        "observed_preview_token",
        "completed_preview_token",
        "stimulus preview running",
        "stimulus preview completed",
        "audio_state",
        "--event-width-codes",
        "event_width_codes",
        "audio_gain",
        "widthCode",
        "oscillator.frequency.value = 880",
        "setStatus(`ready",
        "Point the real webcam at this window",
    ] {
        assert!(
            LOCAL_STIMULUS.contains(expected),
            "local stimulus should contain {expected}"
        );
    }
}

#[test]
fn manual_probe_python_servers_use_reentrant_state_locks() {
    const BROWSER_CONSUMER: &str = include_str!(concat!(
        env!("CARGO_MANIFEST_DIR"),
        "/scripts/manual/browser_consumer_probe.py"
    ));
    for (name, script) in [
        ("local stimulus", LOCAL_STIMULUS),
        ("browser consumer", BROWSER_CONSUMER),
    ] {
        assert!(
            script.contains("threading.RLock()"),
            "{name} server request handlers call snapshot while holding state lock; use RLock to avoid /start deadlocks"
        );
    }
}

#[test]
fn manual_browser_probe_scripts_stay_under_loc_guard() {
    for (name, contents) in [
        ("run_upstream_browser_av_sync.sh", BROWSER_SYNC_SCRIPT),
        (
            "run_google_meet_observer_probe.sh",
            GOOGLE_MEET_OBSERVER_SCRIPT,
        ),
        ("local_av_stimulus.py", LOCAL_STIMULUS),
    ] {
        let loc = contents.lines().count();
        assert!(
            loc <= 500,
            "{name} should stay under the 500 LOC refactor guard; saw {loc}"
        );
    }
}
