/**
 * Tests for nested object resolution with deltas containing 3+ pointers
 * This tests the complex case where a single delta establishes relationships
 * between multiple entities and includes scalar values.
 */

import { RhizomeNode } from '../src/node';
import { Delta } from '../src/core';
import { DefaultSchemaRegistry } from '../src/schema';
import { SchemaBuilder, PrimitiveSchemas, ReferenceSchemas, SchemaAppliedViewWithNesting } from '../src/schema';
import { TypedCollectionImpl } from '../src/collections';

describe('Multi-Pointer Delta Resolution', () => {
  let node: RhizomeNode;
  let schemaRegistry: DefaultSchemaRegistry;

  beforeEach(() => {
    node = new RhizomeNode();
    schemaRegistry = new DefaultSchemaRegistry();
  });

  describe('Three-Entity Relationship Deltas', () => {
    it('should handle movie casting deltas with actor, movie, role, and scalars', async () => {
      // Create schemas for a movie casting scenario
      const actorSchema = SchemaBuilder
        .create('actor')
        .name('Actor')
        .property('name', PrimitiveSchemas.requiredString())
        .property('filmography', ReferenceSchemas.to('casting-summary', 3))
        .required('name')
        .build();

      const movieSchema = SchemaBuilder
        .create('movie')
        .name('Movie')
        .property('title', PrimitiveSchemas.requiredString())
        .property('cast', ReferenceSchemas.to('casting-summary', 3))
        .required('title')
        .build();

      const roleSchema = SchemaBuilder
        .create('role')
        .name('Role')
        .property('name', PrimitiveSchemas.requiredString())
        .property('portrayals', ReferenceSchemas.to('casting-summary', 3))
        .required('name')
        .build();

      const castingSummarySchema = SchemaBuilder
        .create('casting-summary')
        .name('Casting Summary')
        .property('name', PrimitiveSchemas.string())
        .property('title', PrimitiveSchemas.string())
        .property('salary', PrimitiveSchemas.number())
        .property('contract_date', PrimitiveSchemas.string())
        .additionalProperties(false)
        .build();

      schemaRegistry.register(actorSchema);
      schemaRegistry.register(movieSchema);
      schemaRegistry.register(roleSchema);
      schemaRegistry.register(castingSummarySchema);

      // Create collections
      const actorCollection = new TypedCollectionImpl<{ name: string }>('actors', actorSchema, schemaRegistry);
      const movieCollection = new TypedCollectionImpl<{ title: string }>('movies', movieSchema, schemaRegistry);
      const roleCollection = new TypedCollectionImpl<{ name: string }>('roles', roleSchema, schemaRegistry);

      actorCollection.rhizomeConnect(node);
      movieCollection.rhizomeConnect(node);
      roleCollection.rhizomeConnect(node);

      // Create entities
      await actorCollection.put('keanu', { name: 'Keanu Reeves' });
      await movieCollection.put('matrix', { title: 'The Matrix' });
      await roleCollection.put('neo', { name: 'Neo' });

      // Create a complex casting delta with multiple entity references and scalar values
      const castingDelta = new Delta({
        creator: node.config.creator,
        host: node.config.peerId,
        pointers: [
          { localContext: 'actors', target: 'keanu', targetContext: 'filmography' },
          { localContext: 'movies', target: 'matrix', targetContext: 'cast' },
          { localContext: 'roles', target: 'neo', targetContext: 'portrayals' },
          { localContext: 'salary', target: 15000000 },
          { localContext: 'contract_date', target: '1999-03-31' }
        ]
      });
      node.lossless.ingestDelta(castingDelta);

      // Test from Keanu's perspective
      const keanuViews = node.lossless.view(['keanu']);
      const keanuView = keanuViews['keanu'];

      expect(keanuView.propertyDeltas.filmography).toBeDefined();
      expect(keanuView.propertyDeltas.filmography.length).toBe(1);

      const nestedKeanuView = schemaRegistry.applySchemaWithNesting(
        keanuView,
        'actor',
        node.lossless,
        { maxDepth: 2 }
      );

      expect(nestedKeanuView.id).toBe('keanu');
      
      // Should resolve references to matrix and neo, but not keanu (self)
      expect(nestedKeanuView.nestedObjects.filmography).toBeDefined();
      if (nestedKeanuView.nestedObjects.filmography) {
        const castingEntry = nestedKeanuView.nestedObjects.filmography[0];
        expect(castingEntry).toBeDefined();
        
        // The casting entry should be resolved with casting-summary schema
        expect(castingEntry.schemaId).toBe('casting-summary');
        
        // Should not contain a reference to keanu (the parent)
        expect(castingEntry.id).not.toBe('keanu');
      }

      // Test from Matrix's perspective
      const matrixViews = node.lossless.view(['matrix']);
      const matrixView = matrixViews['matrix'];

      const nestedMatrixView = schemaRegistry.applySchemaWithNesting(
        matrixView,
        'movie',
        node.lossless,
        { maxDepth: 2 }
      );

      expect(nestedMatrixView.id).toBe('matrix');
      expect(nestedMatrixView.nestedObjects.cast).toBeDefined();
    });

    it('should handle deltas with mixed scalar and reference values correctly', async () => {
      // Create a simpler schema for testing mixed values
      const personSchema = SchemaBuilder
        .create('person')
        .name('Person')
        .property('name', PrimitiveSchemas.requiredString())
        .property('relationships', ReferenceSchemas.to('relationship-summary', 3))
        .required('name')
        .build();

      const relationshipSummarySchema = SchemaBuilder
        .create('relationship-summary')
        .name('Relationship Summary')
        .property('partner_name', PrimitiveSchemas.string())
        .property('type', PrimitiveSchemas.string())
        .property('since', PrimitiveSchemas.string())
        .property('intensity', PrimitiveSchemas.number())
        .additionalProperties(false)
        .build();

      schemaRegistry.register(personSchema);
      schemaRegistry.register(relationshipSummarySchema);

      const personCollection = new TypedCollectionImpl<{ name: string }>('people', personSchema, schemaRegistry);
      personCollection.rhizomeConnect(node);

      // Create people
      await personCollection.put('alice', { name: 'Alice' });
      await personCollection.put('bob', { name: 'Bob' });

      // Create a relationship delta with one entity reference and multiple scalars
      const relationshipDelta = new Delta({
        creator: node.config.creator,
        host: node.config.peerId,
        pointers: [
          { localContext: 'people', target: 'alice', targetContext: 'relationships' },
          { localContext: 'partner', target: 'bob' }, // Entity reference
          { localContext: 'type', target: 'friendship' }, // Scalar
          { localContext: 'since', target: '2020-01-15' }, // Scalar
          { localContext: 'intensity', target: 8 } // Scalar number
        ]
      });
      node.lossless.ingestDelta(relationshipDelta);

      // Test from Alice's perspective
      const aliceViews = node.lossless.view(['alice']);
      const aliceView = aliceViews['alice'];

      const nestedAliceView = schemaRegistry.applySchemaWithNesting(
        aliceView,
        'person',
        node.lossless,
        { maxDepth: 2 }
      );

      expect(nestedAliceView.id).toBe('alice');
      expect(nestedAliceView.nestedObjects.relationships).toBeDefined();
      
      if (nestedAliceView.nestedObjects.relationships) {
        expect(nestedAliceView.nestedObjects.relationships.length).toBe(1);
        const relationshipEntry = nestedAliceView.nestedObjects.relationships[0];
        
        // Should be resolved with relationship-summary schema
        expect(relationshipEntry.schemaId).toBe('relationship-summary');
        
        // Should contain scalar values and reference to bob but not alice
        expect(relationshipEntry.id).not.toBe('alice');
        
        // The relationship should contain the scalar values from the delta
        // Note: The exact structure depends on how the resolution logic handles mixed values
      }
    });

    it('should correctly identify multiple entity references within a single delta', async () => {
      // Test a scenario with multiple entity references that should all be resolved
      const projectSchema = SchemaBuilder
        .create('project')
        .name('Project')
        .property('name', PrimitiveSchemas.requiredString())
        .property('collaborations', ReferenceSchemas.to('collaboration-summary', 3))
        .required('name')
        .build();

      const collaborationSummarySchema = SchemaBuilder
        .create('collaboration-summary')
        .name('Collaboration Summary')
        .property('project_name', PrimitiveSchemas.string())
        .property('developer_name', PrimitiveSchemas.string())
        .property('designer_name', PrimitiveSchemas.string())
        .property('budget', PrimitiveSchemas.number())
        .additionalProperties(false)
        .build();

      schemaRegistry.register(projectSchema);
      schemaRegistry.register(collaborationSummarySchema);

      const projectCollection = new TypedCollectionImpl<{ name: string }>('projects', projectSchema, schemaRegistry);
      const developerCollection = new TypedCollectionImpl<{ name: string }>('developers', projectSchema, schemaRegistry);
      const designerCollection = new TypedCollectionImpl<{ name: string }>('designers', projectSchema, schemaRegistry);

      projectCollection.rhizomeConnect(node);
      developerCollection.rhizomeConnect(node);
      designerCollection.rhizomeConnect(node);

      // Create entities
      await projectCollection.put('website', { name: 'Company Website' });
      await developerCollection.put('alice', { name: 'Alice Developer' });
      await designerCollection.put('bob', { name: 'Bob Designer' });

      // Create a collaboration delta with multiple entity references
      const collaborationDelta = new Delta({
        creator: node.config.creator,
        host: node.config.peerId,
        pointers: [
          { localContext: 'projects', target: 'website', targetContext: 'collaborations' },
          { localContext: 'developer', target: 'alice' }, // Entity reference
          { localContext: 'designer', target: 'bob' },   // Entity reference  
          { localContext: 'budget', target: 50000 },     // Scalar
          { localContext: 'deadline', target: '2024-06-01' } // Scalar
        ]
      });
      node.lossless.ingestDelta(collaborationDelta);

      // Test from project's perspective
      const projectViews = node.lossless.view(['website']);
      const projectView = projectViews['website'];

      const nestedProjectView = schemaRegistry.applySchemaWithNesting(
        projectView,
        'project',
        node.lossless,
        { maxDepth: 2 }
      );

      expect(nestedProjectView.id).toBe('website');
      expect(nestedProjectView.nestedObjects.collaborations).toBeDefined();
      
      if (nestedProjectView.nestedObjects.collaborations) {
        
        // Verify we get exactly 1 composite object (not 2 separate objects)
        expect(nestedProjectView.nestedObjects.collaborations.length).toBe(1);
        const collaboration = nestedProjectView.nestedObjects.collaborations[0];
        
        expect(collaboration.schemaId).toBe('collaboration-summary');
        expect(collaboration.id).toMatch(/^composite-/); // Should be a synthetic composite ID
        
        // Verify the composite object contains scalar properties
        expect(collaboration.properties.budget).toBe(50000);
        expect(collaboration.properties.deadline).toBe('2024-06-01');
        
        // Verify the composite object contains nested entity references
        expect(collaboration.nestedObjects.developer).toBeDefined();
        expect(collaboration.nestedObjects.designer).toBeDefined();
        
        // The nested entities should be resolved as arrays with single objects
        const developers = collaboration.nestedObjects.developer as SchemaAppliedViewWithNesting[];
        const designers = collaboration.nestedObjects.designer as SchemaAppliedViewWithNesting[];
        
        // Each should have exactly one entity
        expect(developers.length).toBe(1);
        expect(designers.length).toBe(1);
        
        // Verify each entity reference resolves to the correct entity
        expect(developers[0].id).toBe('alice');
        expect(developers[0].schemaId).toBe('collaboration-summary');
        
        expect(designers[0].id).toBe('bob');
        expect(designers[0].schemaId).toBe('collaboration-summary');
      }
    });
  });
});