import "reflect-metadata";
import { 
  MetadataRegistry, 
  SchemaBuilder 
} from "../../core";
import { Neo4jAdapter } from "../../adapters/neo4j";
import { User } from "../../examples/blog/User";
import { Post } from "../../examples/blog/Post";
import { Tag } from "../../examples/blog/Tag";

// Add Neo4j-specific metadata for test
Reflect.defineMetadata("neo4j:label", "BlogUser", User);
Reflect.defineMetadata("neo4j:relationship", { type: "AUTHORED", direction: "OUT" }, User.prototype, "posts");

describe("Neo4jAdapter", () => {
  let registry: MetadataRegistry;
  let builder: SchemaBuilder;
  let adapter: Neo4jAdapter;

  beforeEach(() => {
    // Set up registry, builder, and adapter for each test
    registry = new MetadataRegistry();
    builder = new SchemaBuilder(registry);
    
    // Register entities
    builder.registerEntities([User, Post, Tag]);
    
    // Create adapter with mock connection
    adapter = new Neo4jAdapter(registry, "bolt://localhost:7687");
  });

  test("should handle entities with custom Neo4j labels", async () => {
    // We can indirectly verify the label handling through query operation
    // which internally uses getNodeLabel
    await adapter.query(User, { id: "test" });
    
    // No assertions needed as we're just checking it doesn't throw
    // We could use a spy on console.log to verify the label if needed
  });

  test("should initialize with connection string", () => {
    // Create a new adapter with a different connection string
    const testAdapter = new Neo4jAdapter(registry, "bolt://neo4j:7687");
    
    // Just verify it was created successfully
    expect(testAdapter).toBeInstanceOf(Neo4jAdapter);
  });
  
  test("should handle query operations with different criteria", async () => {
    // Test querying by ID
    const resultById = await adapter.query(User, { id: "user1" });
    expect(resultById).toBeNull();
    
    // Test querying by email
    const resultByEmail = await adapter.query(User, { email: "test@example.com" });
    expect(resultByEmail).toBeNull();
  });
  
  test("should handle relationship data in entity operations", () => {
    // Verify that relationships are used in adapter operations through
    // type-specific metadata, which we can't easily test directly
    const metadata = Reflect.getMetadata("neo4j:relationship", User.prototype, "posts");
    expect(metadata).toHaveProperty("type", "AUTHORED");
    
    // This ensures the relationship metadata we're setting is correctly read
    expect(metadata).toHaveProperty("direction", "OUT");
  });

  test("should support all required repository operations", async () => {
    // Verify queryMany operation
    const results = await adapter.queryMany(User, { active: true });
    expect(Array.isArray(results)).toBe(true);
    
    // Verify delete operation
    await adapter.delete(User, "test123");
    
    // No assertions needed here - just checking the operations don't throw
  });

  test("should handle query operations", async () => {
    // Should not throw when querying by ID
    await expect(adapter.query(User, { id: "test123" })).resolves.toBeNull();
    
    // Should not throw when querying by criteria
    await expect(adapter.query(User, { email: "test@example.com" })).resolves.toBeNull();
  });

  test("should handle queryMany operations", async () => {
    // Should not throw when querying for multiple entities
    await expect(adapter.queryMany(User, { active: true })).resolves.toEqual([]);
  });
  
  test("should handle delete operations", async () => {
    // Should not throw when deleting an entity
    await expect(adapter.delete(User, "user123")).resolves.not.toThrow();
  });
  
  test("should handle runNativeQuery operations", async () => {
    // Should not throw when running a native query
    const query = "MATCH (u:User) WHERE u.id = $id RETURN u";
    const params = { id: "user123" };
    
    await expect(adapter.runNativeQuery(query, params)).resolves.not.toThrow();
  });
});