// TODO(3): clean up unwrap_used as part of broader gpt5_reasoner lint conformance pass.
#![allow(clippy::unwrap_used)]

//! Integration tests for `gpt5_reasoner` logging and `output_filename` behavior.
//!
//! These tests verify the return value semantics of the `request()` function:
//! - When `output_filename` is `Some` and `prompt_type` is `Plan`, returns a path string
//! - When `output_filename` is `None`, returns content directly
//!
//! Note: Full integration tests requiring the `OpenRouter` API are not run in CI.
//! These tests focus on verifying the expected return value shapes and contracts.

/// Verify that a path returned by `gpt5_reasoner` with `output_filename` follows the expected format.
///
/// Expected format: `./thoughts/{work_dir}/plans/{filename}`
#[test]
fn plan_path_format_is_valid() {
    // Example path that would be returned when output_filename is set
    let example_path = "./thoughts/feat-example/plans/my_plan.md";

    // Verify path structure
    assert!(
        example_path.starts_with("./thoughts/"),
        "Path should start with ./thoughts/"
    );
    assert!(
        example_path.contains("/plans/"),
        "Path should contain /plans/ subdirectory"
    );
    assert!(
        std::path::Path::new(example_path)
            .extension()
            .is_some_and(|ext| ext.eq_ignore_ascii_case("md")),
        "Path should end with .md extension"
    );

    // Parse and validate structure
    let parts: Vec<&str> = example_path.split('/').collect();
    assert_eq!(parts.len(), 5, "Path should have 5 components");
    assert_eq!(parts[0], ".", "First component should be '.'");
    assert_eq!(
        parts[1], "thoughts",
        "Second component should be 'thoughts'"
    );
    // parts[2] is the work directory (variable)
    assert_eq!(parts[3], "plans", "Fourth component should be 'plans'");
    // parts[4] is the filename
}

/// Verify that content returned without `output_filename` is not a path.
#[test]
fn content_return_is_not_path() {
    // Example markdown content that would be returned when output_filename is None
    let example_content = r"# Implementation Plan

## Phase 1: Setup

This is the implementation plan content...
";

    // Verify this is content, not a path
    assert!(
        !example_content.starts_with("./thoughts/"),
        "Content should not look like a path"
    );
    assert!(
        example_content.contains('#'),
        "Content should contain markdown headers"
    );
    assert!(
        example_content.len() > 50,
        "Content should be substantial markdown"
    );
}

/// Verify the request JSON structure logged for Plan requests includes `output_filename`.
#[test]
fn request_json_shape_includes_output_filename() {
    // Simulate the JSON structure logged in orchestration.rs
    let request_json = serde_json::json!({
        "prompt_type": "plan",
        "prompt": "Design a REST API",
        "directories": null,
        "files_count": 3,
        "output_filename": "api_design.md",
    });

    // Verify expected fields exist
    assert!(request_json.get("prompt_type").is_some());
    assert!(request_json.get("prompt").is_some());
    assert!(request_json.get("output_filename").is_some());
    assert!(request_json.get("files_count").is_some());

    // Verify output_filename value
    assert_eq!(
        request_json["output_filename"].as_str(),
        Some("api_design.md")
    );
    assert_eq!(request_json["prompt_type"].as_str(), Some("plan"));
}

/// Verify the request JSON structure for Reasoning requests (`output_filename` is null).
#[test]
fn request_json_shape_reasoning_mode() {
    let request_json = serde_json::json!({
        "prompt_type": "reasoning",
        "prompt": "Explain how async/await works",
        "directories": null,
        "files_count": 1,
        "output_filename": null,
    });

    assert_eq!(request_json["prompt_type"].as_str(), Some("reasoning"));
    assert!(request_json["output_filename"].is_null());
}

/// Test that `ToolCallRecord` can be serialized with expected fields.
#[test]
fn tool_call_record_serialization() {
    use agentic_logging::CallTimer;
    use agentic_logging::ToolCallRecord;

    // Use CallTimer to get proper timestamps
    let timer = CallTimer::start();
    let (completed_at, duration_ms) = timer.finish();

    let record = ToolCallRecord {
        call_id: "test-uuid".into(),
        server: "gpt5_reasoner".into(),
        tool: "plan".into(),
        started_at: timer.started_at,
        completed_at,
        duration_ms,
        request: serde_json::json!({"prompt": "test"}),
        response_file: Some("test.md".into()),
        success: true,
        error: None,
        failure_kind: None,
        model: Some("openai/gpt-5.2".into()),
        token_usage: None,
        summary: None,
    };

    let json = serde_json::to_string(&record).unwrap();

    // Verify key fields are present
    assert!(json.contains("\"call_id\":\"test-uuid\""));
    assert!(json.contains("\"server\":\"gpt5_reasoner\""));
    assert!(json.contains("\"tool\":\"plan\""));
    assert!(json.contains("\"response_file\":\"test.md\""));
    assert!(json.contains("\"success\":true"));
    assert!(json.contains("\"model\":\"openai/gpt-5.2\""));

    // Verify None fields are omitted (skip_serializing_if)
    assert!(!json.contains("\"error\""));
    assert!(!json.contains("\"token_usage\""));
    assert!(!json.contains("\"summary\""));
}

#[test]
fn tool_call_record_serialization_includes_summary_when_present() {
    use agentic_logging::CallTimer;
    use agentic_logging::ToolCallRecord;

    let timer = CallTimer::start();
    let (completed_at, duration_ms) = timer.finish();

    let record = ToolCallRecord {
        call_id: "test-uuid".into(),
        server: "gpt5_reasoner".into(),
        tool: "reasoning".into(),
        started_at: timer.started_at,
        completed_at,
        duration_ms,
        request: serde_json::json!({"prompt": "test"}),
        response_file: None,
        success: true,
        error: None,
        failure_kind: None,
        model: Some("openai/gpt-5.2".into()),
        token_usage: None,
        summary: Some(serde_json::json!({"attempt_index": 0, "response_id": "resp_123"})),
    };

    let json = serde_json::to_string(&record).unwrap();
    assert!(json.contains("\"summary\""));
    assert!(json.contains("\"response_id\":\"resp_123\""));
}

/// Test that `WriteDocumentOk` path format matches expected structure.
#[test]
fn write_document_ok_path_format() {
    use thoughts_tool::WriteDocumentOk;

    let ok = WriteDocumentOk {
        path: "./thoughts/my-branch/plans/design.md".into(),
        bytes_written: 2048,
        github_url: None,
    };

    // Path should follow the expected format
    assert!(ok.path.starts_with("./thoughts/"));
    assert!(
        std::path::Path::new(&ok.path)
            .extension()
            .is_some_and(|ext| ext.eq_ignore_ascii_case("md"))
    );
    assert!(ok.bytes_written > 0);
}
