package application_test

import (
	"math/rand"
	"testing"
	"time"

	"source.quilibrium.com/quilibrium/monorepo/node/hypergraph/application"
)

type Operation struct {
	Type      string // "AddVertex", "RemoveVertex", "AddHyperedge", "RemoveHyperedge"
	Vertex    *application.Vertex
	Hyperedge *application.Hyperedge
}

func TestConvergence(t *testing.T) {
	numParties := 3
	numOperations := 100

	// Generate a set of vertices and hyperedges
	vertices := make([]*application.Vertex, numOperations)
	for i := 0; i < numOperations; i++ {
		vertices[i] = &application.Vertex{
			AppAddress:   [32]byte{byte(i % 256)},
			DataAddress:  [32]byte{byte(i / 256)},
			SegmentOrder: uint16(i),
		}
	}

	hyperedges := make([]*application.Hyperedge, numOperations/10)
	for i := 0; i < numOperations/10; i++ {
		hyperedges[i] = &application.Hyperedge{
			AppAddress:  [32]byte{byte(i % 256)},
			DataAddress: [32]byte{byte(i / 256)},
			Extrinsics:  make(map[[66]byte]application.Atom),
		}
		// Add some random vertices as extrinsics
		for j := 0; j < 3; j++ {
			v := vertices[rand.Intn(len(vertices))]
			hyperedges[i].Extrinsics[v.GetID()] = v
		}
	}

	// Generate a sequence of operations
	operations1 := make([]Operation, numOperations)
	operations2 := make([]Operation, numOperations)
	for i := 0; i < numOperations; i++ {
		op := rand.Intn(2)
		switch op {
		case 0:
			operations1[i] = Operation{Type: "AddVertex", Vertex: vertices[rand.Intn(len(vertices))]}
		case 1:
			operations1[i] = Operation{Type: "RemoveVertex", Vertex: vertices[rand.Intn(len(vertices))]}
		}
	}
	for i := 0; i < numOperations; i++ {
		op := rand.Intn(2)
		switch op {
		case 0:
			operations2[i] = Operation{Type: "AddHyperedge", Hyperedge: hyperedges[rand.Intn(len(hyperedges))]}
		case 1:
			operations2[i] = Operation{Type: "RemoveHyperedge", Hyperedge: hyperedges[rand.Intn(len(hyperedges))]}
		}
	}

	// Create CRDTs for each party
	crdts := make([]*application.Hypergraph, numParties)
	for i := 0; i < numParties; i++ {
		crdts[i] = application.NewHypergraph()
	}

	// Apply operations in different orders for each party
	for i := 0; i < numParties; i++ {
		rand.Seed(time.Now().UnixNano())
		rand.Shuffle(len(operations1), func(i, j int) { operations1[i], operations1[j] = operations1[j], operations1[i] })
		rand.Shuffle(len(operations2), func(i, j int) { operations2[i], operations2[j] = operations2[j], operations2[i] })

		for _, op := range operations1 {
			switch op.Type {
			case "AddVertex":
				crdts[i].AddVertex(op.Vertex)
			case "RemoveVertex":
				crdts[i].RemoveVertex(op.Vertex)
			case "AddHyperedge":
				crdts[i].AddHyperedge(op.Hyperedge)
			case "RemoveHyperedge":
				crdts[i].RemoveHyperedge(op.Hyperedge)
			}
		}
		for _, op := range operations2 {
			switch op.Type {
			case "AddVertex":
				crdts[i].AddVertex(op.Vertex)
			case "RemoveVertex":
				crdts[i].RemoveVertex(op.Vertex)
			case "AddHyperedge":
				crdts[i].AddHyperedge(op.Hyperedge)
			case "RemoveHyperedge":
				crdts[i].RemoveHyperedge(op.Hyperedge)
			}
		}
	}

	// Verify that all CRDTs have converged to the same state
	// Additional verification: check specific vertices and hyperedges
	for _, v := range vertices {
		state := crdts[0].LookupVertex(v)
		for i := 1; i < numParties; i++ {
			if crdts[i].LookupVertex(v) != state {
				t.Errorf("Vertex %v has different state in CRDT %d", v, i)
			}
		}
	}

	for _, h := range hyperedges {
		state := crdts[0].LookupHyperedge(h)
		for i := 1; i < numParties; i++ {
			if crdts[i].LookupHyperedge(h) != state {
				t.Errorf("Hyperedge %v has different state in CRDT %d", h, i)
			}
		}
	}
}