use super::*;

use crate::sandbox_tags::sandbox_tag;
use codex_protocol::models::PermissionProfile;
use codex_protocol::openai_models::ReasoningEffort as ReasoningEffortConfig;
use codex_protocol::protocol::SandboxPolicy;
use codex_protocol::protocol::ThreadSource;
use core_test_support::PathBufExt;
use core_test_support::PathExt;
use pretty_assertions::assert_eq;
use serde_json::Value;
use std::collections::HashMap;
use tempfile::TempDir;
use tokio::process::Command;

fn test_mcp_turn_metadata_context() -> McpTurnMetadataContext<'static> {
    McpTurnMetadataContext {
        model: "gpt-5.4",
        reasoning_effort: Some(ReasoningEffortConfig::High),
    }
}

#[tokio::test]
async fn build_turn_metadata_header_includes_has_changes_for_clean_repo() {
    let temp_dir = TempDir::new().expect("temp dir");
    let repo_path = temp_dir.path().join("repo-東京").abs();
    std::fs::create_dir_all(&repo_path).expect("create repo");

    Command::new("git")
        .args(["init"])
        .current_dir(&repo_path)
        .output()
        .await
        .expect("git init");
    Command::new("git")
        .args(["config", "user.name", "Test User"])
        .current_dir(&repo_path)
        .output()
        .await
        .expect("git config user.name");
    Command::new("git")
        .args(["config", "user.email", "test@example.com"])
        .current_dir(&repo_path)
        .output()
        .await
        .expect("git config user.email");

    std::fs::write(repo_path.join("README.md"), "hello").expect("write file");
    Command::new("git")
        .args(["add", "."])
        .current_dir(&repo_path)
        .output()
        .await
        .expect("git add");
    Command::new("git")
        .args(["commit", "-m", "initial"])
        .current_dir(&repo_path)
        .output()
        .await
        .expect("git commit");

    let header = build_turn_metadata_header(&repo_path, Some("none"))
        .await
        .expect("header");
    assert!(header.is_ascii());
    assert!(!header.contains("東京"));
    let parsed: Value = serde_json::from_str(&header).expect("valid json");
    let expected_repo_path = repo_path.to_string_lossy().into_owned();
    let actual_repo_path = parsed
        .get("workspaces")
        .and_then(Value::as_object)
        .and_then(|workspaces| workspaces.keys().next())
        .expect("workspace path");
    assert_eq!(actual_repo_path, &expected_repo_path);
    let workspace = parsed
        .get("workspaces")
        .and_then(Value::as_object)
        .and_then(|workspaces| workspaces.values().next())
        .cloned()
        .expect("workspace");

    assert_eq!(
        workspace.get("has_changes").and_then(Value::as_bool),
        Some(false)
    );
}

#[test]
fn turn_metadata_state_uses_platform_sandbox_tag() {
    let temp_dir = TempDir::new().expect("temp dir");
    let cwd = temp_dir.path().abs();
    let sandbox_policy = SandboxPolicy::new_read_only_policy();
    let permission_profile = PermissionProfile::read_only();

    let state = TurnMetadataState::new(
        "session-a".to_string(),
        "thread-a".to_string(),
        Some(ThreadSource::User),
        "turn-a".to_string(),
        cwd,
        &permission_profile,
        WindowsSandboxLevel::Disabled,
        /*enforce_managed_network*/ false,
    );

    let header = state.current_header_value().expect("header");
    let json: Value = serde_json::from_str(&header).expect("json");
    let sandbox_name = json.get("sandbox").and_then(Value::as_str);
    let session_id = json.get("session_id").and_then(Value::as_str);
    let thread_id = json.get("thread_id").and_then(Value::as_str);
    let thread_source = json.get("thread_source").and_then(Value::as_str);

    let expected_sandbox = sandbox_tag(&sandbox_policy, WindowsSandboxLevel::Disabled);
    assert_eq!(sandbox_name, Some(expected_sandbox));
    assert_eq!(session_id, Some("session-a"));
    assert_eq!(thread_id, Some("thread-a"));
    assert_eq!(thread_source, Some("user"));
    assert!(json.get("session_source").is_none());
}

#[test]
fn turn_metadata_state_uses_explicit_subagent_thread_source() {
    let temp_dir = TempDir::new().expect("temp dir");
    let cwd = temp_dir.path().abs();
    let permission_profile = PermissionProfile::read_only();
    let state = TurnMetadataState::new(
        "session-a".to_string(),
        "thread-a".to_string(),
        Some(ThreadSource::Subagent),
        "turn-a".to_string(),
        cwd,
        &permission_profile,
        WindowsSandboxLevel::Disabled,
        /*enforce_managed_network*/ false,
    );

    let header = state.current_header_value().expect("header");
    let json: Value = serde_json::from_str(&header).expect("json");

    assert_eq!(json["thread_source"].as_str(), Some("subagent"));
    assert!(json.get("session_source").is_none());
}

#[test]
fn turn_metadata_state_includes_turn_started_at_unix_ms_after_start() {
    let temp_dir = TempDir::new().expect("temp dir");
    let cwd = temp_dir.path().abs();
    let permission_profile = PermissionProfile::read_only();

    let state = TurnMetadataState::new(
        "session-a".to_string(),
        "thread-a".to_string(),
        Some(ThreadSource::User),
        "turn-a".to_string(),
        cwd,
        &permission_profile,
        WindowsSandboxLevel::Disabled,
        /*enforce_managed_network*/ false,
    );
    state.set_turn_started_at_unix_ms(/*turn_started_at_unix_ms*/ 1_700_000_000_123);

    let header = state.current_header_value().expect("header");
    let json: Value = serde_json::from_str(&header).expect("json");

    assert_eq!(
        json["turn_started_at_unix_ms"].as_i64(),
        Some(1_700_000_000_123)
    );
}

#[test]
fn turn_metadata_state_includes_model_and_reasoning_effort_only_in_request_meta() {
    let temp_dir = TempDir::new().expect("temp dir");
    let cwd = temp_dir.path().abs();
    let permission_profile = PermissionProfile::read_only();

    let state = TurnMetadataState::new(
        "session-a".to_string(),
        "thread-a".to_string(),
        /*thread_source*/ None,
        "turn-a".to_string(),
        cwd,
        &permission_profile,
        WindowsSandboxLevel::Disabled,
        /*enforce_managed_network*/ false,
    );

    let header = state.current_header_value().expect("header");
    let header_json: Value = serde_json::from_str(&header).expect("json");
    assert!(header_json.get("model").is_none());
    assert!(header_json.get("reasoning_effort").is_none());

    let meta = state
        .current_meta_value_for_mcp_request(test_mcp_turn_metadata_context())
        .expect("turn metadata should be present");
    assert_eq!(meta["model"].as_str(), Some("gpt-5.4"));
    assert_eq!(meta["reasoning_effort"].as_str(), Some("high"));

    let meta_without_reasoning_effort = state
        .current_meta_value_for_mcp_request(McpTurnMetadataContext {
            model: "gpt-5.4",
            reasoning_effort: None,
        })
        .expect("turn metadata should be present");
    assert_eq!(
        meta_without_reasoning_effort["model"].as_str(),
        Some("gpt-5.4")
    );
    assert!(
        meta_without_reasoning_effort
            .get("reasoning_effort")
            .is_none()
    );
}

#[test]
fn turn_metadata_state_ignores_client_turn_started_at_unix_ms_before_start() {
    let temp_dir = TempDir::new().expect("temp dir");
    let cwd = temp_dir.path().abs();
    let permission_profile = PermissionProfile::read_only();

    let state = TurnMetadataState::new(
        "session-a".to_string(),
        "thread-a".to_string(),
        Some(ThreadSource::User),
        "turn-a".to_string(),
        cwd,
        &permission_profile,
        WindowsSandboxLevel::Disabled,
        /*enforce_managed_network*/ false,
    );
    state.set_responsesapi_client_metadata(HashMap::from([(
        "turn_started_at_unix_ms".to_string(),
        "client-supplied".to_string(),
    )]));

    let header = state.current_header_value().expect("header");
    let json: Value = serde_json::from_str(&header).expect("json");

    assert!(json.get("turn_started_at_unix_ms").is_none());
}

#[test]
fn turn_metadata_state_merges_client_metadata_without_replacing_reserved_fields() {
    let temp_dir = TempDir::new().expect("temp dir");
    let cwd = temp_dir.path().abs();
    let permission_profile = PermissionProfile::read_only();

    let state = TurnMetadataState::new(
        "session-a".to_string(),
        "thread-a".to_string(),
        Some(ThreadSource::User),
        "turn-a".to_string(),
        cwd,
        &permission_profile,
        WindowsSandboxLevel::Disabled,
        /*enforce_managed_network*/ false,
    );
    state.set_responsesapi_client_metadata(HashMap::from([
        ("fiber_run_id".to_string(), "fiber-123".to_string()),
        ("origin".to_string(), "東京".to_string()),
        ("model".to_string(), "client-supplied".to_string()),
        (
            "reasoning_effort".to_string(),
            "client-supplied".to_string(),
        ),
        ("session_id".to_string(), "client-supplied".to_string()),
        ("thread_id".to_string(), "client-supplied".to_string()),
        ("thread_source".to_string(), "client-supplied".to_string()),
        (
            "turn_started_at_unix_ms".to_string(),
            "client-supplied".to_string(),
        ),
    ]));
    state.set_turn_started_at_unix_ms(/*turn_started_at_unix_ms*/ 1_700_000_000_123);

    let header = state.current_header_value().expect("header");
    assert!(header.is_ascii());
    assert!(!header.contains("東京"));
    let json: Value = serde_json::from_str(&header).expect("json");

    assert_eq!(json["fiber_run_id"].as_str(), Some("fiber-123"));
    assert_eq!(json["origin"].as_str(), Some("東京"));
    assert_eq!(json["model"].as_str(), Some("client-supplied"));
    assert_eq!(json["reasoning_effort"].as_str(), Some("client-supplied"));
    assert_eq!(json["session_id"].as_str(), Some("session-a"));
    assert_eq!(json["thread_id"].as_str(), Some("thread-a"));
    assert_eq!(json["thread_source"].as_str(), Some("user"));
    assert_eq!(json["turn_id"].as_str(), Some("turn-a"));
    assert_eq!(
        json["turn_started_at_unix_ms"].as_i64(),
        Some(1_700_000_000_123)
    );

    let meta = state
        .current_meta_value_for_mcp_request(test_mcp_turn_metadata_context())
        .expect("turn metadata should be present");
    assert_eq!(meta["model"].as_str(), Some("gpt-5.4"));
    assert_eq!(meta["reasoning_effort"].as_str(), Some("high"));
}
