//go:build !(js && wasm)

package database

import (
	"bytes"
	"context"
	"testing"

	"github.com/dgraph-io/badger/v4"
	"next.orly.dev/pkg/database/indexes"
	"next.orly.dev/pkg/database/indexes/types"
	"git.mleku.dev/mleku/nostr/encoders/event"
	"git.mleku.dev/mleku/nostr/encoders/hex"
	"git.mleku.dev/mleku/nostr/encoders/tag"
)

func TestETagGraphEdgeCreation(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	db, err := New(ctx, cancel, t.TempDir(), "info")
	if err != nil {
		t.Fatalf("Failed to create database: %v", err)
	}
	defer db.Close()

	// Create a parent event (the post being replied to)
	parentPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
	parentID := make([]byte, 32)
	parentID[0] = 0x10
	parentSig := make([]byte, 64)
	parentSig[0] = 0x10

	parentEvent := &event.E{
		ID:        parentID,
		Pubkey:    parentPubkey,
		CreatedAt: 1234567890,
		Kind:      1,
		Content:   []byte("This is the parent post"),
		Sig:       parentSig,
		Tags:      &tag.S{},
	}
	_, err = db.SaveEvent(ctx, parentEvent)
	if err != nil {
		t.Fatalf("Failed to save parent event: %v", err)
	}

	// Create a reply event with e-tag pointing to parent
	replyPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
	replyID := make([]byte, 32)
	replyID[0] = 0x20
	replySig := make([]byte, 64)
	replySig[0] = 0x20

	replyEvent := &event.E{
		ID:        replyID,
		Pubkey:    replyPubkey,
		CreatedAt: 1234567891,
		Kind:      1,
		Content:   []byte("This is a reply"),
		Sig:       replySig,
		Tags: tag.NewS(
			tag.NewFromAny("e", hex.Enc(parentID)),
		),
	}
	_, err = db.SaveEvent(ctx, replyEvent)
	if err != nil {
		t.Fatalf("Failed to save reply event: %v", err)
	}

	// Get serials for both events
	parentSerial, err := db.GetSerialById(parentID)
	if err != nil {
		t.Fatalf("Failed to get parent serial: %v", err)
	}
	replySerial, err := db.GetSerialById(replyID)
	if err != nil {
		t.Fatalf("Failed to get reply serial: %v", err)
	}

	t.Logf("Parent serial: %d, Reply serial: %d", parentSerial.Get(), replySerial.Get())

	// Verify forward edge exists (reply -> parent)
	forwardFound := false
	prefix := []byte(indexes.EventEventGraphPrefix)

	err = db.View(func(txn *badger.Txn) error {
		it := txn.NewIterator(badger.DefaultIteratorOptions)
		defer it.Close()

		for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
			item := it.Item()
			key := item.KeyCopy(nil)

			// Decode the key
			srcSer, tgtSer, kind, direction := indexes.EventEventGraphVars()
			keyReader := bytes.NewReader(key)
			if err := indexes.EventEventGraphDec(srcSer, tgtSer, kind, direction).UnmarshalRead(keyReader); err != nil {
				t.Logf("Failed to decode key: %v", err)
				continue
			}

			// Check if this is our edge
			if srcSer.Get() == replySerial.Get() && tgtSer.Get() == parentSerial.Get() {
				forwardFound = true
				if direction.Letter() != types.EdgeDirectionETagOut {
					t.Errorf("Expected direction %d, got %d", types.EdgeDirectionETagOut, direction.Letter())
				}
				if kind.Get() != 1 {
					t.Errorf("Expected kind 1, got %d", kind.Get())
				}
			}
		}
		return nil
	})
	if err != nil {
		t.Fatalf("View failed: %v", err)
	}
	if !forwardFound {
		t.Error("Forward edge (reply -> parent) should exist")
	}

	// Verify reverse edge exists (parent <- reply)
	reverseFound := false
	prefix = []byte(indexes.GraphEventEventPrefix)

	err = db.View(func(txn *badger.Txn) error {
		it := txn.NewIterator(badger.DefaultIteratorOptions)
		defer it.Close()

		for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
			item := it.Item()
			key := item.KeyCopy(nil)

			// Decode the key
			tgtSer, kind, direction, srcSer := indexes.GraphEventEventVars()
			keyReader := bytes.NewReader(key)
			if err := indexes.GraphEventEventDec(tgtSer, kind, direction, srcSer).UnmarshalRead(keyReader); err != nil {
				t.Logf("Failed to decode key: %v", err)
				continue
			}

			t.Logf("Found gee edge: tgt=%d kind=%d dir=%d src=%d",
				tgtSer.Get(), kind.Get(), direction.Letter(), srcSer.Get())

			// Check if this is our edge
			if tgtSer.Get() == parentSerial.Get() && srcSer.Get() == replySerial.Get() {
				reverseFound = true
				if direction.Letter() != types.EdgeDirectionETagIn {
					t.Errorf("Expected direction %d, got %d", types.EdgeDirectionETagIn, direction.Letter())
				}
				if kind.Get() != 1 {
					t.Errorf("Expected kind 1, got %d", kind.Get())
				}
			}
		}
		return nil
	})
	if err != nil {
		t.Fatalf("View failed: %v", err)
	}
	if !reverseFound {
		t.Error("Reverse edge (parent <- reply) should exist")
	}
}

func TestETagGraphMultipleReplies(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	db, err := New(ctx, cancel, t.TempDir(), "info")
	if err != nil {
		t.Fatalf("Failed to create database: %v", err)
	}
	defer db.Close()

	// Create a parent event
	parentPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
	parentID := make([]byte, 32)
	parentID[0] = 0x10
	parentSig := make([]byte, 64)
	parentSig[0] = 0x10

	parentEvent := &event.E{
		ID:        parentID,
		Pubkey:    parentPubkey,
		CreatedAt: 1234567890,
		Kind:      1,
		Content:   []byte("Parent post"),
		Sig:       parentSig,
		Tags:      &tag.S{},
	}
	_, err = db.SaveEvent(ctx, parentEvent)
	if err != nil {
		t.Fatalf("Failed to save parent: %v", err)
	}

	// Create multiple replies
	numReplies := 5
	for i := 0; i < numReplies; i++ {
		replyPubkey := make([]byte, 32)
		replyPubkey[0] = byte(i + 0x20)
		replyID := make([]byte, 32)
		replyID[0] = byte(i + 0x30)
		replySig := make([]byte, 64)
		replySig[0] = byte(i + 0x30)

		replyEvent := &event.E{
			ID:        replyID,
			Pubkey:    replyPubkey,
			CreatedAt: int64(1234567891 + i),
			Kind:      1,
			Content:   []byte("Reply"),
			Sig:       replySig,
			Tags: tag.NewS(
				tag.NewFromAny("e", hex.Enc(parentID)),
			),
		}
		_, err := db.SaveEvent(ctx, replyEvent)
		if err != nil {
			t.Fatalf("Failed to save reply %d: %v", i, err)
		}
	}

	// Count inbound edges to parent
	parentSerial, err := db.GetSerialById(parentID)
	if err != nil {
		t.Fatalf("Failed to get parent serial: %v", err)
	}

	inboundCount := 0
	prefix := []byte(indexes.GraphEventEventPrefix)

	err = db.View(func(txn *badger.Txn) error {
		it := txn.NewIterator(badger.DefaultIteratorOptions)
		defer it.Close()

		for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
			item := it.Item()
			key := item.KeyCopy(nil)

			tgtSer, kind, direction, srcSer := indexes.GraphEventEventVars()
			keyReader := bytes.NewReader(key)
			if err := indexes.GraphEventEventDec(tgtSer, kind, direction, srcSer).UnmarshalRead(keyReader); err != nil {
				continue
			}

			if tgtSer.Get() == parentSerial.Get() {
				inboundCount++
			}
		}
		return nil
	})
	if err != nil {
		t.Fatalf("View failed: %v", err)
	}

	if inboundCount != numReplies {
		t.Errorf("Expected %d inbound edges, got %d", numReplies, inboundCount)
	}
}

func TestETagGraphDifferentKinds(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	db, err := New(ctx, cancel, t.TempDir(), "info")
	if err != nil {
		t.Fatalf("Failed to create database: %v", err)
	}
	defer db.Close()

	// Create a parent event (kind 1 - note)
	parentPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
	parentID := make([]byte, 32)
	parentID[0] = 0x10
	parentSig := make([]byte, 64)
	parentSig[0] = 0x10

	parentEvent := &event.E{
		ID:        parentID,
		Pubkey:    parentPubkey,
		CreatedAt: 1234567890,
		Kind:      1,
		Content:   []byte("A note"),
		Sig:       parentSig,
		Tags:      &tag.S{},
	}
	_, err = db.SaveEvent(ctx, parentEvent)
	if err != nil {
		t.Fatalf("Failed to save parent: %v", err)
	}

	// Create a reaction (kind 7)
	reactionPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
	reactionID := make([]byte, 32)
	reactionID[0] = 0x20
	reactionSig := make([]byte, 64)
	reactionSig[0] = 0x20

	reactionEvent := &event.E{
		ID:        reactionID,
		Pubkey:    reactionPubkey,
		CreatedAt: 1234567891,
		Kind:      7,
		Content:   []byte("+"),
		Sig:       reactionSig,
		Tags: tag.NewS(
			tag.NewFromAny("e", hex.Enc(parentID)),
		),
	}
	_, err = db.SaveEvent(ctx, reactionEvent)
	if err != nil {
		t.Fatalf("Failed to save reaction: %v", err)
	}

	// Create a repost (kind 6)
	repostPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
	repostID := make([]byte, 32)
	repostID[0] = 0x30
	repostSig := make([]byte, 64)
	repostSig[0] = 0x30

	repostEvent := &event.E{
		ID:        repostID,
		Pubkey:    repostPubkey,
		CreatedAt: 1234567892,
		Kind:      6,
		Content:   []byte(""),
		Sig:       repostSig,
		Tags: tag.NewS(
			tag.NewFromAny("e", hex.Enc(parentID)),
		),
	}
	_, err = db.SaveEvent(ctx, repostEvent)
	if err != nil {
		t.Fatalf("Failed to save repost: %v", err)
	}

	// Query inbound edges by kind
	parentSerial, err := db.GetSerialById(parentID)
	if err != nil {
		t.Fatalf("Failed to get parent serial: %v", err)
	}

	kindCounts := make(map[uint16]int)
	prefix := []byte(indexes.GraphEventEventPrefix)

	err = db.View(func(txn *badger.Txn) error {
		it := txn.NewIterator(badger.DefaultIteratorOptions)
		defer it.Close()

		for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
			item := it.Item()
			key := item.KeyCopy(nil)

			tgtSer, kind, direction, srcSer := indexes.GraphEventEventVars()
			keyReader := bytes.NewReader(key)
			if err := indexes.GraphEventEventDec(tgtSer, kind, direction, srcSer).UnmarshalRead(keyReader); err != nil {
				continue
			}

			if tgtSer.Get() == parentSerial.Get() {
				kindCounts[kind.Get()]++
			}
		}
		return nil
	})
	if err != nil {
		t.Fatalf("View failed: %v", err)
	}

	// Verify we have edges for each kind
	if kindCounts[7] != 1 {
		t.Errorf("Expected 1 kind-7 (reaction) edge, got %d", kindCounts[7])
	}
	if kindCounts[6] != 1 {
		t.Errorf("Expected 1 kind-6 (repost) edge, got %d", kindCounts[6])
	}
}

func TestETagGraphUnknownTarget(t *testing.T) {
	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	db, err := New(ctx, cancel, t.TempDir(), "info")
	if err != nil {
		t.Fatalf("Failed to create database: %v", err)
	}
	defer db.Close()

	// Create an event with e-tag pointing to non-existent event
	unknownID := make([]byte, 32)
	unknownID[0] = 0xFF
	unknownID[31] = 0xFF

	replyPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
	replyID := make([]byte, 32)
	replyID[0] = 0x10
	replySig := make([]byte, 64)
	replySig[0] = 0x10

	replyEvent := &event.E{
		ID:        replyID,
		Pubkey:    replyPubkey,
		CreatedAt: 1234567890,
		Kind:      1,
		Content:   []byte("Reply to unknown"),
		Sig:       replySig,
		Tags: tag.NewS(
			tag.NewFromAny("e", hex.Enc(unknownID)),
		),
	}
	_, err = db.SaveEvent(ctx, replyEvent)
	if err != nil {
		t.Fatalf("Failed to save reply: %v", err)
	}

	// Verify event was saved
	replySerial, err := db.GetSerialById(replyID)
	if err != nil {
		t.Fatalf("Failed to get reply serial: %v", err)
	}
	if replySerial == nil {
		t.Fatal("Reply serial should exist")
	}

	// Verify no forward edge was created (since target doesn't exist)
	edgeCount := 0
	prefix := []byte(indexes.EventEventGraphPrefix)

	err = db.View(func(txn *badger.Txn) error {
		it := txn.NewIterator(badger.DefaultIteratorOptions)
		defer it.Close()

		for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
			item := it.Item()
			key := item.KeyCopy(nil)

			srcSer, _, _, _ := indexes.EventEventGraphVars()
			keyReader := bytes.NewReader(key)
			if err := indexes.EventEventGraphDec(srcSer, new(types.Uint40), new(types.Uint16), new(types.Letter)).UnmarshalRead(keyReader); err != nil {
				continue
			}

			if srcSer.Get() == replySerial.Get() {
				edgeCount++
			}
		}
		return nil
	})
	if err != nil {
		t.Fatalf("View failed: %v", err)
	}

	if edgeCount != 0 {
		t.Errorf("Expected no edges for unknown target, got %d", edgeCount)
	}
}
