//! Integration tests for the ToSchema derive macro

#![allow(dead_code)]

use herolib_toschema::ToSchema;

// Basic struct with primitive types
#[derive(ToSchema)]
struct BasicStruct {
    name: String,
    count: u32,
    enabled: bool,
    score: f64,
}

// Struct with optional fields
#[derive(ToSchema)]
struct OptionalFields {
    required_field: String,
    optional_field: Option<String>,
    optional_number: Option<i32>,
}

// Struct with collections
#[derive(ToSchema)]
struct CollectionStruct {
    items: Vec<String>,
    numbers: Vec<i32>,
}

// Nested struct
#[derive(ToSchema)]
struct InnerStruct {
    value: i32,
}

#[derive(ToSchema)]
struct OuterStruct {
    name: String,
    inner: InnerStruct,
}

// Complex struct similar to NodeSpec
#[derive(ToSchema)]
struct HardwareSpec {
    hardware_cost_usd: f32,
    power_consumption_watts: f32,
    rack_units: f32,
}

#[derive(ToSchema)]
struct CapacitySpec {
    memory_gb: u32,
    vcpu: u32,
    ssd_gb: u32,
    hdd_gb: u32,
    nr_gpu: u32,
    passmark: u32,
    llm_tps_performance_70b: u32,
}

#[derive(ToSchema)]
struct AgentSpec {
    max_agents: u32,
    agent_type: String,
}

#[derive(ToSchema)]
struct SliceSpec {
    nr_slices: u32,
}

#[derive(ToSchema)]
struct PricingSpec {
    node_price_per_slice_per_month: f64,
    agent_price_per_month: f64,
    hdd_price_per_gb_per_month: f64,
}

#[derive(ToSchema)]
struct NodeSpec {
    name: String,
    hardware: HardwareSpec,
    capacity: CapacitySpec,
    agents: AgentSpec,
    slices: SliceSpec,
    pricing: PricingSpec,
}

#[test]
fn test_basic_struct_schema() {
    let schema = BasicStruct::json_schema();
    assert!(schema.contains(r#""type": "object""#));
    assert!(schema.contains(r#""name": {"type": "string"}"#));
    assert!(schema.contains(r#""count": {"type": "integer"}"#));
    assert!(schema.contains(r#""enabled": {"type": "boolean"}"#));
    assert!(schema.contains(r#""score": {"type": "number"}"#));
}

#[test]
fn test_optional_fields_schema() {
    let schema = OptionalFields::json_schema();
    assert!(schema.contains(r#""required_field": {"type": "string"}"#));
    assert!(schema.contains(r#""optional_field": {"type": ["string", "null"]}"#));
    assert!(schema.contains(r#""optional_number": {"type": ["integer", "null"]}"#));
}

#[test]
fn test_collection_struct_schema() {
    let schema = CollectionStruct::json_schema();
    assert!(schema.contains(r#""type": "array""#));
    assert!(schema.contains(r#""items": {"type": "string"}"#));
}

#[test]
fn test_nested_struct_schema_inlined() {
    // The inner struct should be fully inlined, not just a $ref
    let schema = OuterStruct::json_schema();
    println!("OuterStruct schema:\n{}", OuterStruct::json_schema_pretty());

    // Should contain the actual InnerStruct definition inline
    assert!(schema.contains(r#""inner": {"type": "object""#));
    assert!(schema.contains(r#""value": {"type": "integer"}"#));
}

#[test]
fn test_pretty_print() {
    let schema = BasicStruct::json_schema_pretty();

    // Pretty print should contain newlines and indentation
    assert!(schema.contains('\n'));
    assert!(schema.contains("  ")); // indentation
}

#[test]
fn test_hardware_spec_schema() {
    let schema = HardwareSpec::json_schema();
    assert!(schema.contains(r#""hardware_cost_usd": {"type": "number"}"#));
    assert!(schema.contains(r#""power_consumption_watts": {"type": "number"}"#));
    assert!(schema.contains(r#""rack_units": {"type": "number"}"#));
}

#[test]
fn test_capacity_spec_schema() {
    let schema = CapacitySpec::json_schema();
    assert!(schema.contains(r#""memory_gb": {"type": "integer"}"#));
    assert!(schema.contains(r#""vcpu": {"type": "integer"}"#));
    assert!(schema.contains(r#""ssd_gb": {"type": "integer"}"#));
    assert!(schema.contains(r#""nr_gpu": {"type": "integer"}"#));
}

#[test]
fn test_pricing_spec_schema() {
    let schema = PricingSpec::json_schema();
    assert!(schema.contains(r#""node_price_per_slice_per_month": {"type": "number"}"#));
    assert!(schema.contains(r#""agent_price_per_month": {"type": "number"}"#));
}

#[test]
fn print_nodespec_schema_pretty() {
    // This test prints the schema for visual inspection
    let schema = NodeSpec::json_schema_pretty();
    println!("\n=== NodeSpec JSON Schema (fully inlined) ===\n{}", schema);
}

#[test]
fn test_nodespec_full_schema_output() {
    // Test that NodeSpec contains fully inlined nested types
    let schema = NodeSpec::json_schema_pretty();

    println!("\n=== Full NodeSpec Schema ===\n{}", schema);

    // Verify it's a valid object schema
    assert!(schema.contains(r#""$schema": "http://json-schema.org/draft-07/schema#""#));
    assert!(schema.contains(r#""title": "NodeSpec""#));
    assert!(schema.contains(r#""type": "object""#));

    // Verify nested types are INLINED (not $ref)
    // HardwareSpec fields should be present
    assert!(schema.contains(r#""hardware_cost_usd""#));
    assert!(schema.contains(r#""power_consumption_watts""#));
    assert!(schema.contains(r#""rack_units""#));

    // CapacitySpec fields should be present
    assert!(schema.contains(r#""memory_gb""#));
    assert!(schema.contains(r#""vcpu""#));
    assert!(schema.contains(r#""ssd_gb""#));
    assert!(schema.contains(r#""hdd_gb""#));
    assert!(schema.contains(r#""nr_gpu""#));
    assert!(schema.contains(r#""passmark""#));
    assert!(schema.contains(r#""llm_tps_performance_70b""#));

    // AgentSpec fields should be present
    assert!(schema.contains(r#""max_agents""#));
    assert!(schema.contains(r#""agent_type""#));

    // SliceSpec fields should be present
    assert!(schema.contains(r#""nr_slices""#));

    // PricingSpec fields should be present
    assert!(schema.contains(r#""node_price_per_slice_per_month""#));
    assert!(schema.contains(r#""agent_price_per_month""#));
    assert!(schema.contains(r#""hdd_price_per_gb_per_month""#));

    // Should NOT contain $ref (everything is inlined)
    assert!(
        !schema.contains("$ref"),
        "Schema should not contain $ref - all types should be inlined"
    );
}

#[test]
fn test_schema_object_method() {
    // Test that schema_object() returns just the object definition without $schema header
    let obj_schema = HardwareSpec::schema_object();

    assert!(
        !obj_schema.contains("$schema"),
        "schema_object() should not include $schema header"
    );
    assert!(obj_schema.contains(r#""type": "object""#));
    assert!(obj_schema.contains(r#""hardware_cost_usd""#));
}

#[test]
fn test_inner_struct_schema_object() {
    let schema = InnerStruct::schema_object();
    println!("InnerStruct schema_object: {}", schema);

    assert!(schema.contains(r#""type": "object""#));
    assert!(schema.contains(r#""value": {"type": "integer"}"#));
}

// Simple enum with unit variants only
#[derive(ToSchema)]
enum Color {
    Red,
    Green,
    Blue,
}

// Enum with unit variants including more options
#[derive(ToSchema)]
enum Status {
    Pending,
    Active,
    Completed,
    Failed,
    Cancelled,
}

// Complex enum with mixed variant types
#[derive(ToSchema)]
enum Message {
    Quit,
    Move { x: i32, y: i32 },
    Write(String),
    ChangeColor(i32, i32, i32),
}

// Enum with tuple variants
#[derive(ToSchema)]
enum Event {
    Click(i32, i32),
    KeyPress(String),
    Scroll(f64),
}

// Struct containing an enum field
#[derive(ToSchema)]
struct Task {
    name: String,
    status: Status,
}

#[test]
fn test_simple_enum_schema() {
    let schema = Color::json_schema();
    println!("Color schema: {}", schema);

    // Should use JSON Schema enum keyword
    assert!(schema.contains(r#""enum""#));
    assert!(schema.contains(r#""Red""#));
    assert!(schema.contains(r#""Green""#));
    assert!(schema.contains(r#""Blue""#));

    // Should NOT contain type: string (enums use enum keyword directly)
    assert!(!schema.contains(r#""type": "string""#));
}

#[test]
fn test_simple_enum_schema_pretty() {
    let schema = Color::json_schema_pretty();
    println!("\n=== Color Enum Schema ===\n{}", schema);

    // Verify structure
    assert!(schema.contains(r#""$schema": "http://json-schema.org/draft-07/schema#""#));
    assert!(schema.contains(r#""title": "Color""#));
    assert!(schema.contains(r#""enum""#));
}

#[test]
fn test_status_enum_schema() {
    let schema = Status::json_schema();
    println!("Status schema: {}", schema);

    assert!(schema.contains(r#""Pending""#));
    assert!(schema.contains(r#""Active""#));
    assert!(schema.contains(r#""Completed""#));
    assert!(schema.contains(r#""Failed""#));
    assert!(schema.contains(r#""Cancelled""#));
}

#[test]
fn test_complex_enum_schema() {
    let schema = Message::json_schema_pretty();
    println!("\n=== Message Enum Schema (complex) ===\n{}", schema);

    // Should use oneOf for complex enums
    assert!(schema.contains(r#""oneOf""#));

    // Unit variant should use const
    assert!(schema.contains(r#""const": "Quit""#));

    // Struct variant should have properties
    assert!(schema.contains(r#""Move""#));
    assert!(schema.contains(r#""x""#));
    assert!(schema.contains(r#""y""#));

    // Tuple variant with single element
    assert!(schema.contains(r#""Write""#));

    // Tuple variant with multiple elements
    assert!(schema.contains(r#""ChangeColor""#));
}

#[test]
fn test_tuple_variant_enum_schema() {
    let schema = Event::json_schema_pretty();
    println!("\n=== Event Enum Schema (tuple variants) ===\n{}", schema);

    assert!(schema.contains(r#""oneOf""#));
    assert!(schema.contains(r#""Click""#));
    assert!(schema.contains(r#""KeyPress""#));
    assert!(schema.contains(r#""Scroll""#));
}

#[test]
fn test_struct_with_enum_field() {
    let schema = Task::json_schema_pretty();
    println!("\n=== Task Struct with Enum Field ===\n{}", schema);

    // Should contain the status field with enum values
    assert!(schema.contains(r#""status""#));
    assert!(schema.contains(r#""enum""#));
    assert!(schema.contains(r#""Pending""#));
}

#[test]
fn test_enum_schema_object() {
    // Test that schema_object() returns just the enum definition without $schema header
    let obj_schema = Color::schema_object();
    println!("Color schema_object: {}", obj_schema);

    assert!(
        !obj_schema.contains("$schema"),
        "schema_object() should not include $schema header"
    );
    assert!(obj_schema.contains(r#""enum""#));
    assert!(obj_schema.contains(r#""Red""#));
}

#[test]
fn test_simple_enum_full_string_comparison() {
    // Full string comparison test for simple enum schema
    let schema = Color::json_schema_pretty();
    println!("\n=== Color Enum Full Schema ===\n{}", schema);

    let expected = r#"{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "Color",
  "enum": [
    "Red",
    "Green",
    "Blue"
  ]
}"#;

    assert_eq!(
        schema, expected,
        "\n\n=== ACTUAL SCHEMA ===\n{}\n\n=== EXPECTED SCHEMA ===\n{}\n",
        schema, expected
    );
}

#[test]
fn test_complex_enum_full_output() {
    // Print full schema for complex enum to verify structure
    let schema = Message::json_schema_pretty();
    println!("\n=== Message Complex Enum Full Schema ===\n{}", schema);

    // Verify all variant types are present
    // Unit variant: Quit
    assert!(
        schema.contains(r#""const": "Quit""#),
        "Missing Quit unit variant"
    );

    // Struct variant: Move { x, y }
    assert!(schema.contains(r#""Move""#), "Missing Move struct variant");
    assert!(schema.contains(r#""x""#), "Missing x field in Move");
    assert!(schema.contains(r#""y""#), "Missing y field in Move");

    // Tuple variant with single element: Write(String)
    assert!(schema.contains(r#""Write""#), "Missing Write tuple variant");

    // Tuple variant with multiple elements: ChangeColor(i32, i32, i32)
    assert!(
        schema.contains(r#""ChangeColor""#),
        "Missing ChangeColor tuple variant"
    );
    assert!(
        schema.contains(r#""minItems": 3"#),
        "Missing minItems for ChangeColor"
    );
    assert!(
        schema.contains(r#""maxItems": 3"#),
        "Missing maxItems for ChangeColor"
    );
}

#[test]
fn test_nodespec_full_string_comparison() {
    // Full string comparison test for NodeSpec schema
    let schema = NodeSpec::json_schema_pretty();

    let expected = r#"{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "title": "NodeSpec",
  "type": "object",
  "properties": {
    "name": {
      "type": "string"
    },
    "hardware": {
      "type": "object",
      "properties": {
        "hardware_cost_usd": {
          "type": "number"
        },
        "power_consumption_watts": {
          "type": "number"
        },
        "rack_units": {
          "type": "number"
        }
      },
      "required": [
        "hardware_cost_usd",
        "power_consumption_watts",
        "rack_units"
      ]
    },
    "capacity": {
      "type": "object",
      "properties": {
        "memory_gb": {
          "type": "integer"
        },
        "vcpu": {
          "type": "integer"
        },
        "ssd_gb": {
          "type": "integer"
        },
        "hdd_gb": {
          "type": "integer"
        },
        "nr_gpu": {
          "type": "integer"
        },
        "passmark": {
          "type": "integer"
        },
        "llm_tps_performance_70b": {
          "type": "integer"
        }
      },
      "required": [
        "memory_gb",
        "vcpu",
        "ssd_gb",
        "hdd_gb",
        "nr_gpu",
        "passmark",
        "llm_tps_performance_70b"
      ]
    },
    "agents": {
      "type": "object",
      "properties": {
        "max_agents": {
          "type": "integer"
        },
        "agent_type": {
          "type": "string"
        }
      },
      "required": [
        "max_agents",
        "agent_type"
      ]
    },
    "slices": {
      "type": "object",
      "properties": {
        "nr_slices": {
          "type": "integer"
        }
      },
      "required": [
        "nr_slices"
      ]
    },
    "pricing": {
      "type": "object",
      "properties": {
        "node_price_per_slice_per_month": {
          "type": "number"
        },
        "agent_price_per_month": {
          "type": "number"
        },
        "hdd_price_per_gb_per_month": {
          "type": "number"
        }
      },
      "required": [
        "node_price_per_slice_per_month",
        "agent_price_per_month",
        "hdd_price_per_gb_per_month"
      ]
    }
  },
  "required": [
    "name",
    "hardware",
    "capacity",
    "agents",
    "slices",
    "pricing"
  ]
}"#;

    assert_eq!(
        schema, expected,
        "\n\n=== ACTUAL SCHEMA ===\n{}\n\n=== EXPECTED SCHEMA ===\n{}\n",
        schema, expected
    );
}
