// Copyright 2026 khive contributors. Licensed under Apache-2.0.
//
//! Integration tests for [`JsonFormatAdapter`].
//!
//! These tests drive the public API — no access to private fields.
//! They cover the three required scenarios from issue #366:
//!   1. Roundtrip: entities + edges survive parse → re-serialize
//!   2. Empty input: valid JSON empty array produces zero records
//!   3. Malformed input: invalid JSON returns a parse error, no panic

use khive_vcs_adapters::{
    AdapterError, EdgeRecord, EntityRecord, FormatAdapter, JsonFormatAdapter,
};
use serde_json::Value;

// ---------------------------------------------------------------------------
// Fixtures
// ---------------------------------------------------------------------------

const ENTITY_FIXTURE_JSON: &str = r#"[
  {
    "id": "11111111-1111-1111-1111-111111111111",
    "kind": "concept",
    "name": "FlashAttention",
    "description": "IO-aware exact attention",
    "tags": ["attention", "efficiency"],
    "properties": { "year": 2022 }
  },
  {
    "id": "22222222-2222-2222-2222-222222222222",
    "kind": "person",
    "name": "Tri Dao"
  }
]"#;

const EDGE_FIXTURE_JSON: &str = r#"[
  {
    "edge_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "source": "11111111-1111-1111-1111-111111111111",
    "target": "22222222-2222-2222-2222-222222222222",
    "relation": "introduced_by",
    "weight": 1.0
  }
]"#;

const MIXED_FIXTURE_JSON: &str = r#"[
  {
    "id": "11111111-1111-1111-1111-111111111111",
    "kind": "concept",
    "name": "FlashAttention"
  },
  {
    "id": "22222222-2222-2222-2222-222222222222",
    "kind": "person",
    "name": "Tri Dao"
  },
  {
    "edge_id": "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
    "source": "11111111-1111-1111-1111-111111111111",
    "target": "22222222-2222-2222-2222-222222222222",
    "relation": "introduced_by",
    "weight": 0.9
  }
]"#;

// ---------------------------------------------------------------------------
// Test 1 — roundtrip
// ---------------------------------------------------------------------------

#[test]
fn test_json_adapter_roundtrip() {
    // --- Entities ---
    let mut adapter =
        JsonFormatAdapter::new(ENTITY_FIXTURE_JSON).expect("valid JSON must not fail construction");

    assert_eq!(adapter.name(), "json");

    let entities: Vec<EntityRecord> = adapter
        .entities()
        .map(|r| r.expect("fixture has no errors"))
        .collect();

    assert_eq!(entities.len(), 2, "two entities expected");

    // First entity: FlashAttention
    let fa = &entities[0];
    assert_eq!(fa.id.to_string(), "11111111-1111-1111-1111-111111111111");
    assert_eq!(fa.kind, "concept");
    assert_eq!(fa.name, "FlashAttention");
    assert_eq!(fa.description.as_deref(), Some("IO-aware exact attention"));
    assert_eq!(fa.tags, vec!["attention", "efficiency"]);

    // Extra 'properties' key should be merged
    let year = fa
        .properties
        .get("year")
        .and_then(|v| v.as_i64())
        .expect("year should be in properties");
    assert_eq!(year, 2022);

    // Second entity: Tri Dao (no description, no tags)
    let td = &entities[1];
    assert_eq!(td.kind, "person");
    assert_eq!(td.name, "Tri Dao");
    assert!(td.description.is_none());
    assert!(td.tags.is_empty());

    // No edges in an entity-only array
    let edges: Vec<EdgeRecord> = adapter
        .edges()
        .map(|r| r.expect("no edge errors"))
        .collect();
    assert!(edges.is_empty(), "no edges expected from entity-only input");

    // No warnings
    assert!(adapter.warnings().is_empty(), "no warnings expected");

    // --- Edges ---
    let mut adapter2 =
        JsonFormatAdapter::new(EDGE_FIXTURE_JSON).expect("valid JSON must not fail construction");

    let edges2: Vec<EdgeRecord> = adapter2
        .edges()
        .map(|r| r.expect("fixture has no errors"))
        .collect();
    assert_eq!(edges2.len(), 1, "one edge expected");

    let e = &edges2[0];
    assert_eq!(
        e.edge_id.to_string(),
        "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
    );
    assert_eq!(e.source, "11111111-1111-1111-1111-111111111111");
    assert_eq!(e.target, "22222222-2222-2222-2222-222222222222");
    assert_eq!(e.relation, "introduced_by");
    assert!((e.weight - 1.0).abs() < f64::EPSILON);

    // Re-serialize and check the relation survives
    let json = serde_json::to_string(e).expect("edges serialize to JSON");
    let parsed: Value = serde_json::from_str(&json).expect("re-parse");
    assert_eq!(parsed["relation"], "introduced_by");

    // --- Mixed ---
    let mut adapter3 =
        JsonFormatAdapter::new(MIXED_FIXTURE_JSON).expect("mixed fixture is valid JSON");

    let ents3: Vec<_> = adapter3
        .entities()
        .map(|r| r.expect("no entity errors"))
        .collect();
    let edgs3: Vec<_> = adapter3
        .edges()
        .map(|r| r.expect("no edge errors"))
        .collect();

    assert_eq!(ents3.len(), 2, "two entities in mixed fixture");
    assert_eq!(edgs3.len(), 1, "one edge in mixed fixture");
    assert!((edgs3[0].weight - 0.9).abs() < f64::EPSILON);
}

// ---------------------------------------------------------------------------
// Test 2 — empty input produces valid empty JSON (zero records, no errors)
// ---------------------------------------------------------------------------

#[test]
fn test_json_adapter_handles_empty() {
    let mut adapter = JsonFormatAdapter::new("[]").expect("empty array is valid JSON");

    let entities: Vec<_> = adapter.entities().collect();
    let edges: Vec<_> = adapter.edges().collect();

    assert!(entities.is_empty(), "empty input: no entities");
    assert!(edges.is_empty(), "empty input: no edges");
    assert!(adapter.warnings().is_empty(), "empty input: no warnings");

    // Serialize the empty results to confirm nothing panics and the output is valid
    let entity_json =
        serde_json::to_string(&Vec::<EntityRecord>::new()).expect("empty entity vec serializes");
    let edge_json =
        serde_json::to_string(&Vec::<EdgeRecord>::new()).expect("empty edge vec serializes");

    assert_eq!(entity_json, "[]");
    assert_eq!(edge_json, "[]");
}

// ---------------------------------------------------------------------------
// Test 3 — malformed input returns an error, does not panic
// ---------------------------------------------------------------------------

#[test]
fn test_json_adapter_rejects_malformed() {
    // Case A: not valid JSON at all
    let result = JsonFormatAdapter::new("{ not json }");
    assert!(
        matches!(result, Err(AdapterError::Parse(_))),
        "non-JSON must produce AdapterError::Parse"
    );

    // Case B: valid JSON but not an array (object)
    let result = JsonFormatAdapter::new(r#"{"key": "value"}"#);
    assert!(
        matches!(result, Err(AdapterError::Parse(_))),
        "JSON object at top level must produce AdapterError::Parse"
    );

    // Case C: valid JSON but not an array (bare string)
    let result = JsonFormatAdapter::new(r#""just a string""#);
    assert!(
        matches!(result, Err(AdapterError::Parse(_))),
        "JSON string at top level must produce AdapterError::Parse"
    );

    // Case D: array with a valid-structure entity missing the required 'name' field
    let mut adapter = JsonFormatAdapter::new(r#"[{"kind": "concept"}]"#)
        .expect("structurally valid JSON must construct");
    let first = adapter.entities().next().expect("one record present");
    assert!(
        matches!(first, Err(AdapterError::MissingField { field, .. }) if field == "name"),
        "missing 'name' must produce MissingField error"
    );

    // Case E: edge missing 'relation'
    let mut adapter2 = JsonFormatAdapter::new(r#"[{"source": "aa", "target": "bb"}]"#)
        .expect("structurally valid JSON");
    let first_edge = adapter2.edges().next().expect("one edge record present");
    assert!(
        matches!(first_edge, Err(AdapterError::MissingField { field, .. }) if field == "relation"),
        "missing 'relation' on edge must produce MissingField error"
    );
}

// ---------------------------------------------------------------------------
// Test 4 — unknown extra keys on entities fold into properties
// ---------------------------------------------------------------------------

#[test]
fn test_json_adapter_extra_keys_fold_into_properties() {
    let json = r#"[{
        "id": "33333333-3333-3333-3333-333333333333",
        "kind": "dataset",
        "name": "ImageNet",
        "download_url": "https://image-net.org",
        "num_classes": 1000
    }]"#;

    let mut adapter = JsonFormatAdapter::new(json).expect("valid fixture");
    let ents: Vec<EntityRecord> = adapter.entities().map(|r| r.expect("no errors")).collect();

    assert_eq!(ents.len(), 1);
    let e = &ents[0];
    assert_eq!(e.name, "ImageNet");

    let url = e
        .properties
        .get("download_url")
        .and_then(|v| v.as_str())
        .expect("download_url folded into properties");
    assert_eq!(url, "https://image-net.org");

    let classes = e
        .properties
        .get("num_classes")
        .and_then(|v| v.as_i64())
        .expect("num_classes folded into properties");
    assert_eq!(classes, 1000);
}

// ---------------------------------------------------------------------------
// Test 5 — default weight on edge when absent
// ---------------------------------------------------------------------------

#[test]
fn test_json_adapter_edge_default_weight() {
    let json = r#"[{
        "source": "11111111-1111-1111-1111-111111111111",
        "target": "22222222-2222-2222-2222-222222222222",
        "relation": "extends"
    }]"#;

    let mut adapter = JsonFormatAdapter::new(json).expect("valid");
    let edges: Vec<EdgeRecord> = adapter.edges().map(|r| r.expect("no errors")).collect();

    assert_eq!(edges.len(), 1);
    assert!(
        (edges[0].weight - 0.7).abs() < f64::EPSILON,
        "default weight must be 0.7 (per EdgeRecord default_weight)"
    );
}

// ---------------------------------------------------------------------------
// Test 6 — case-insensitive field lookup (ADR-036 §2)
// ---------------------------------------------------------------------------

#[test]
fn test_json_adapter_case_insensitive_entity_fields() {
    // Mixed-case field names: "Name" and "Kind" should be recognised.
    let json = r#"[{
        "Name": "MixedCaseEntity",
        "Kind": "concept",
        "Description": "upper-cased fields",
        "Tags": ["a", "b"],
        "ID": "44444444-4444-4444-4444-444444444444"
    }]"#;

    let mut adapter = JsonFormatAdapter::new(json).expect("valid fixture");
    let ents: Vec<EntityRecord> = adapter.entities().map(|r| r.expect("no errors")).collect();

    assert_eq!(ents.len(), 1);
    let e = &ents[0];
    assert_eq!(e.name, "MixedCaseEntity");
    assert_eq!(e.kind, "concept");
    assert_eq!(e.description.as_deref(), Some("upper-cased fields"));
    assert_eq!(e.tags, vec!["a", "b"]);
    assert_eq!(e.id.to_string(), "44444444-4444-4444-4444-444444444444");
}

#[test]
fn test_json_adapter_case_insensitive_edge_dispatch() {
    // "Source" and "Target" (capitalised) must trigger edge dispatch.
    let json = r#"[{
        "Source": "11111111-1111-1111-1111-111111111111",
        "Target": "22222222-2222-2222-2222-222222222222",
        "Relation": "extends",
        "Weight": 0.5
    }]"#;

    let mut adapter = JsonFormatAdapter::new(json).expect("valid");
    let edges: Vec<EdgeRecord> = adapter.edges().map(|r| r.expect("no errors")).collect();

    assert_eq!(edges.len(), 1);
    assert_eq!(edges[0].source, "11111111-1111-1111-1111-111111111111");
    assert_eq!(edges[0].target, "22222222-2222-2222-2222-222222222222");
    assert_eq!(edges[0].relation, "extends");
    assert!((edges[0].weight - 0.5).abs() < f64::EPSILON);
}

// ---------------------------------------------------------------------------
// Test 7 — edge properties round-trip (no properties.properties nesting)
// ---------------------------------------------------------------------------

#[test]
fn test_json_adapter_edge_properties_no_double_nesting() {
    // An edge with a top-level "properties" object must round-trip cleanly —
    // EdgeRecord.properties must equal the original object, not nest it under
    // another "properties" key.
    let json = r#"[{
        "source": "11111111-1111-1111-1111-111111111111",
        "target": "22222222-2222-2222-2222-222222222222",
        "relation": "annotates",
        "properties": {"confidence": "high", "note": "verified"}
    }]"#;

    let mut adapter = JsonFormatAdapter::new(json).expect("valid");
    let edges: Vec<EdgeRecord> = adapter.edges().map(|r| r.expect("no errors")).collect();

    assert_eq!(edges.len(), 1);
    let props = &edges[0].properties;

    // Must NOT be nested
    assert!(
        props.get("properties").is_none(),
        "properties must not be nested under 'properties'"
    );

    // Must contain the original keys at the top level
    assert_eq!(
        props.get("confidence").and_then(|v| v.as_str()),
        Some("high")
    );
    assert_eq!(props.get("note").and_then(|v| v.as_str()), Some("verified"));
}

// ---------------------------------------------------------------------------
// Test 8 — byte-identical entity round-trip
// ---------------------------------------------------------------------------

#[test]
fn test_json_adapter_entity_byte_identical_roundtrip() {
    // Build a fully-populated EntityRecord, serialize it, parse it through the
    // adapter, serialize again, and assert byte equality.
    use uuid::Uuid;

    let id: Uuid = "55555555-5555-5555-5555-555555555555".parse().unwrap();
    let original = EntityRecord {
        id,
        kind: "dataset".into(),
        name: "CIFAR-10".into(),
        description: Some("60k images, 10 classes".into()),
        properties: serde_json::json!({"num_classes": 10, "license": "MIT"}),
        tags: vec!["vision".into(), "benchmark".into()],
    };

    // First serialization — this is the wire form the adapter will receive.
    let first_json = serde_json::to_string(&original).expect("EntityRecord serializes");

    // Wrap in an array so the adapter can parse it.
    let array_json = format!("[{}]", first_json);
    let mut adapter = JsonFormatAdapter::new(&array_json).expect("valid JSON");
    let parsed: Vec<EntityRecord> = adapter
        .entities()
        .map(|r| r.expect("no parse errors"))
        .collect();

    assert_eq!(parsed.len(), 1);

    // Second serialization — must be byte-identical to the first.
    let second_json = serde_json::to_string(&parsed[0]).expect("EntityRecord serializes");
    assert_eq!(
        first_json, second_json,
        "entity serialize → parse → serialize must be byte-identical"
    );
}
