package app

import (
	"context"
	"os"
	"testing"
	"time"

	"next.orly.dev/app/config"
	"next.orly.dev/pkg/acl"
	"git.mleku.dev/mleku/nostr/crypto/keys"
	"next.orly.dev/pkg/database"
	"git.mleku.dev/mleku/nostr/encoders/event"
	"git.mleku.dev/mleku/nostr/encoders/hex"
	"git.mleku.dev/mleku/nostr/encoders/tag"
	"git.mleku.dev/mleku/nostr/interfaces/signer/p8k"
	"next.orly.dev/pkg/protocol/nip43"
	"next.orly.dev/pkg/protocol/publish"
)

// setupTestListener creates a test listener with NIP-43 enabled
func setupTestListener(t *testing.T) (*Listener, *database.D, func()) {
	tempDir, err := os.MkdirTemp("", "nip43_handler_test_*")
	if err != nil {
		t.Fatalf("failed to create temp dir: %v", err)
	}

	ctx, cancel := context.WithCancel(context.Background())
	db, err := database.New(ctx, cancel, tempDir, "info")
	if err != nil {
		os.RemoveAll(tempDir)
		t.Fatalf("failed to open database: %v", err)
	}

	cfg := &config.C{
		NIP43Enabled:           true,
		NIP43PublishEvents:     true,
		NIP43PublishMemberList: true,
		NIP43InviteExpiry:      24 * time.Hour,
		RelayURL:               "wss://test.relay",
		Listen:                 "localhost",
		Port:                   3334,
		ACLMode:                "none",
	}

	server := &Server{
		Ctx:           ctx,
		Config:        cfg,
		DB:            db,
		publishers:    publish.New(NewPublisher(ctx)),
		InviteManager: nip43.NewInviteManager(cfg.NIP43InviteExpiry),
		cfg:           cfg,
		db:            db,
	}

	// Configure ACL registry
	acl.Registry.SetMode(cfg.ACLMode)
	if err = acl.Registry.Configure(cfg, db, ctx); err != nil {
		db.Close()
		os.RemoveAll(tempDir)
		t.Fatalf("failed to configure ACL: %v", err)
	}

	listener := &Listener{
		Server:         server,
		ctx:            ctx,
		writeChan:      make(chan publish.WriteRequest, 100),
		writeDone:      make(chan struct{}),
		messageQueue:   make(chan messageRequest, 100),
		processingDone: make(chan struct{}),
		subscriptions:  make(map[string]context.CancelFunc),
	}

	// Start write worker and message processor
	go listener.writeWorker()
	go listener.messageProcessor()

	cleanup := func() {
		// Close listener channels
		close(listener.writeChan)
		<-listener.writeDone
		close(listener.messageQueue)
		<-listener.processingDone
		db.Close()
		os.RemoveAll(tempDir)
	}

	return listener, db, cleanup
}

// TestHandleNIP43JoinRequest_ValidRequest tests a successful join request
func TestHandleNIP43JoinRequest_ValidRequest(t *testing.T) {
	listener, db, cleanup := setupTestListener(t)
	defer cleanup()

	// Generate test user
	userSecret, err := keys.GenerateSecretKey()
	if err != nil {
		t.Fatalf("failed to generate user secret: %v", err)
	}
	userSigner, err := p8k.New()
	if err != nil {
		t.Fatalf("failed to create signer: %v", err)
	}
	if err = userSigner.InitSec(userSecret); err != nil {
		t.Fatalf("failed to initialize signer: %v", err)
	}
	userPubkey := userSigner.Pub()

	// Generate invite code
	code, err := listener.Server.InviteManager.GenerateCode()
	if err != nil {
		t.Fatalf("failed to generate invite code: %v", err)
	}

	// Create join request event
	ev := event.New()
	ev.Kind = nip43.KindJoinRequest
	copy(ev.Pubkey, userPubkey)
	ev.Tags = tag.NewS()
	ev.Tags.Append(tag.NewFromAny("-"))
	ev.Tags.Append(tag.NewFromAny("claim", code))
	ev.CreatedAt = time.Now().Unix()
	ev.Content = []byte("")

	// Sign event
	if err = ev.Sign(userSigner); err != nil {
		t.Fatalf("failed to sign event: %v", err)
	}

	// Handle join request
	err = listener.HandleNIP43JoinRequest(ev)
	if err != nil {
		t.Fatalf("failed to handle join request: %v", err)
	}

	// Verify user was added to database
	isMember, err := db.IsNIP43Member(userPubkey)
	if err != nil {
		t.Fatalf("failed to check membership: %v", err)
	}
	if !isMember {
		t.Error("user was not added as member")
	}

	// Verify membership details
	membership, err := db.GetNIP43Membership(userPubkey)
	if err != nil {
		t.Fatalf("failed to get membership: %v", err)
	}
	if membership.InviteCode != code {
		t.Errorf("wrong invite code stored: got %s, want %s", membership.InviteCode, code)
	}
}

// TestHandleNIP43JoinRequest_InvalidCode tests join request with invalid code
func TestHandleNIP43JoinRequest_InvalidCode(t *testing.T) {
	listener, db, cleanup := setupTestListener(t)
	defer cleanup()

	// Generate test user
	userSecret, err := keys.GenerateSecretKey()
	if err != nil {
		t.Fatalf("failed to generate user secret: %v", err)
	}
	userSigner, err := p8k.New()
	if err != nil {
		t.Fatalf("failed to create signer: %v", err)
	}
	if err = userSigner.InitSec(userSecret); err != nil {
		t.Fatalf("failed to initialize signer: %v", err)
	}
	userPubkey := userSigner.Pub()

	// Create join request with invalid code
	ev := event.New()
	ev.Kind = nip43.KindJoinRequest
	copy(ev.Pubkey, userPubkey)
	ev.Tags = tag.NewS()
	ev.Tags.Append(tag.NewFromAny("-"))
	ev.Tags.Append(tag.NewFromAny("claim", "invalid-code-123"))
	ev.CreatedAt = time.Now().Unix()
	ev.Content = []byte("")

	if err = ev.Sign(userSigner); err != nil {
		t.Fatalf("failed to sign event: %v", err)
	}

	// Handle join request - should succeed but not add member
	err = listener.HandleNIP43JoinRequest(ev)
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}

	// Verify user was NOT added
	isMember, err := db.IsNIP43Member(userPubkey)
	if err != nil {
		t.Fatalf("failed to check membership: %v", err)
	}
	if isMember {
		t.Error("user was incorrectly added as member with invalid code")
	}
}

// TestHandleNIP43JoinRequest_DuplicateMember tests join request from existing member
func TestHandleNIP43JoinRequest_DuplicateMember(t *testing.T) {
	listener, db, cleanup := setupTestListener(t)
	defer cleanup()

	// Generate test user
	userSecret, err := keys.GenerateSecretKey()
	if err != nil {
		t.Fatalf("failed to generate user secret: %v", err)
	}
	userSigner, err := p8k.New()
	if err != nil {
		t.Fatalf("failed to create signer: %v", err)
	}
	if err = userSigner.InitSec(userSecret); err != nil {
		t.Fatalf("failed to initialize signer: %v", err)
	}
	userPubkey := userSigner.Pub()

	// Add user directly to database
	err = db.AddNIP43Member(userPubkey, "original-code")
	if err != nil {
		t.Fatalf("failed to add member: %v", err)
	}

	// Generate new invite code
	code, err := listener.Server.InviteManager.GenerateCode()
	if err != nil {
		t.Fatalf("failed to generate invite code: %v", err)
	}

	// Create join request
	ev := event.New()
	ev.Kind = nip43.KindJoinRequest
	copy(ev.Pubkey, userPubkey)
	ev.Tags = tag.NewS()
	ev.Tags.Append(tag.NewFromAny("-"))
	ev.Tags.Append(tag.NewFromAny("claim", code))
	ev.CreatedAt = time.Now().Unix()
	ev.Content = []byte("")

	if err = ev.Sign(userSigner); err != nil {
		t.Fatalf("failed to sign event: %v", err)
	}

	// Handle join request - should handle gracefully
	err = listener.HandleNIP43JoinRequest(ev)
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}

	// Verify original membership is unchanged
	membership, err := db.GetNIP43Membership(userPubkey)
	if err != nil {
		t.Fatalf("failed to get membership: %v", err)
	}
	if membership.InviteCode != "original-code" {
		t.Errorf("invite code was changed: got %s, want original-code", membership.InviteCode)
	}
}

// TestHandleNIP43LeaveRequest_ValidRequest tests a successful leave request
func TestHandleNIP43LeaveRequest_ValidRequest(t *testing.T) {
	listener, db, cleanup := setupTestListener(t)
	defer cleanup()

	// Generate test user
	userSecret, err := keys.GenerateSecretKey()
	if err != nil {
		t.Fatalf("failed to generate user secret: %v", err)
	}
	userSigner, err := p8k.New()
	if err != nil {
		t.Fatalf("failed to create signer: %v", err)
	}
	if err = userSigner.InitSec(userSecret); err != nil {
		t.Fatalf("failed to initialize signer: %v", err)
	}
	userPubkey := userSigner.Pub()

	// Add user as member
	err = db.AddNIP43Member(userPubkey, "test-code")
	if err != nil {
		t.Fatalf("failed to add member: %v", err)
	}

	// Create leave request
	ev := event.New()
	ev.Kind = nip43.KindLeaveRequest
	copy(ev.Pubkey, userPubkey)
	ev.Tags = tag.NewS()
	ev.Tags.Append(tag.NewFromAny("-"))
	ev.CreatedAt = time.Now().Unix()
	ev.Content = []byte("")

	if err = ev.Sign(userSigner); err != nil {
		t.Fatalf("failed to sign event: %v", err)
	}

	// Handle leave request
	err = listener.HandleNIP43LeaveRequest(ev)
	if err != nil {
		t.Fatalf("failed to handle leave request: %v", err)
	}

	// Verify user was removed
	isMember, err := db.IsNIP43Member(userPubkey)
	if err != nil {
		t.Fatalf("failed to check membership: %v", err)
	}
	if isMember {
		t.Error("user was not removed")
	}
}

// TestHandleNIP43LeaveRequest_NonMember tests leave request from non-member
func TestHandleNIP43LeaveRequest_NonMember(t *testing.T) {
	listener, _, cleanup := setupTestListener(t)
	defer cleanup()

	// Generate test user (not a member)
	userSecret, err := keys.GenerateSecretKey()
	if err != nil {
		t.Fatalf("failed to generate user secret: %v", err)
	}
	userSigner, err := p8k.New()
	if err != nil {
		t.Fatalf("failed to create signer: %v", err)
	}
	if err = userSigner.InitSec(userSecret); err != nil {
		t.Fatalf("failed to initialize signer: %v", err)
	}
	userPubkey := userSigner.Pub()

	// Create leave request
	ev := event.New()
	ev.Kind = nip43.KindLeaveRequest
	copy(ev.Pubkey, userPubkey)
	ev.Tags = tag.NewS()
	ev.Tags.Append(tag.NewFromAny("-"))
	ev.CreatedAt = time.Now().Unix()
	ev.Content = []byte("")

	if err = ev.Sign(userSigner); err != nil {
		t.Fatalf("failed to sign event: %v", err)
	}

	// Handle leave request - should handle gracefully
	err = listener.HandleNIP43LeaveRequest(ev)
	if err != nil {
		t.Fatalf("handler returned error: %v", err)
	}
}

// TestHandleNIP43InviteRequest_ValidRequest tests invite request from admin
func TestHandleNIP43InviteRequest_ValidRequest(t *testing.T) {
	listener, _, cleanup := setupTestListener(t)
	defer cleanup()

	// Generate admin user
	adminSecret, err := keys.GenerateSecretKey()
	if err != nil {
		t.Fatalf("failed to generate admin secret: %v", err)
	}
	adminSigner, err := p8k.New()
	if err != nil {
		t.Fatalf("failed to create signer: %v", err)
	}
	if err = adminSigner.InitSec(adminSecret); err != nil {
		t.Fatalf("failed to initialize signer: %v", err)
	}
	adminPubkey := adminSigner.Pub()

	// Add admin to config and reconfigure ACL
	adminHex := hex.Enc(adminPubkey)
	listener.Server.Config.Admins = []string{adminHex}
	acl.Registry.SetMode("none")
	if err = acl.Registry.Configure(listener.Server.Config, listener.Server.DB, listener.ctx); err != nil {
		t.Fatalf("failed to reconfigure ACL: %v", err)
	}

	// Handle invite request
	inviteEvent, err := listener.Server.HandleNIP43InviteRequest(adminPubkey)
	if err != nil {
		t.Fatalf("failed to handle invite request: %v", err)
	}

	// Verify invite event
	if inviteEvent == nil {
		t.Fatal("invite event is nil")
	}
	if inviteEvent.Kind != nip43.KindInviteReq {
		t.Errorf("wrong event kind: got %d, want %d", inviteEvent.Kind, nip43.KindInviteReq)
	}

	// Verify claim tag
	claimTag := inviteEvent.Tags.GetFirst([]byte("claim"))
	if claimTag == nil {
		t.Fatal("missing claim tag")
	}
	if claimTag.Len() < 2 {
		t.Fatal("claim tag has no value")
	}
}

// TestHandleNIP43InviteRequest_Unauthorized tests invite request from non-admin
func TestHandleNIP43InviteRequest_Unauthorized(t *testing.T) {
	listener, _, cleanup := setupTestListener(t)
	defer cleanup()

	// Generate regular user (not admin)
	userSecret, err := keys.GenerateSecretKey()
	if err != nil {
		t.Fatalf("failed to generate user secret: %v", err)
	}
	userSigner, err := p8k.New()
	if err != nil {
		t.Fatalf("failed to create signer: %v", err)
	}
	if err = userSigner.InitSec(userSecret); err != nil {
		t.Fatalf("failed to initialize signer: %v", err)
	}
	userPubkey := userSigner.Pub()

	// Handle invite request - should fail
	_, err = listener.Server.HandleNIP43InviteRequest(userPubkey)
	if err == nil {
		t.Fatal("expected error for unauthorized user")
	}
}

// TestJoinAndLeaveFlow tests the complete join and leave flow
func TestJoinAndLeaveFlow(t *testing.T) {
	listener, db, cleanup := setupTestListener(t)
	defer cleanup()

	// Generate test user
	userSecret, err := keys.GenerateSecretKey()
	if err != nil {
		t.Fatalf("failed to generate user secret: %v", err)
	}
	userSigner, err := p8k.New()
	if err != nil {
		t.Fatalf("failed to create signer: %v", err)
	}
	if err = userSigner.InitSec(userSecret); err != nil {
		t.Fatalf("failed to initialize signer: %v", err)
	}
	userPubkey := userSigner.Pub()

	// Step 1: Generate invite code
	code, err := listener.Server.InviteManager.GenerateCode()
	if err != nil {
		t.Fatalf("failed to generate invite code: %v", err)
	}

	// Step 2: User sends join request
	joinEv := event.New()
	joinEv.Kind = nip43.KindJoinRequest
	copy(joinEv.Pubkey, userPubkey)
	joinEv.Tags = tag.NewS()
	joinEv.Tags.Append(tag.NewFromAny("-"))
	joinEv.Tags.Append(tag.NewFromAny("claim", code))
	joinEv.CreatedAt = time.Now().Unix()
	joinEv.Content = []byte("")
	if err = joinEv.Sign(userSigner); err != nil {
		t.Fatalf("failed to sign join event: %v", err)
	}

	err = listener.HandleNIP43JoinRequest(joinEv)
	if err != nil {
		t.Fatalf("failed to handle join request: %v", err)
	}

	// Verify user is member
	isMember, err := db.IsNIP43Member(userPubkey)
	if err != nil {
		t.Fatalf("failed to check membership after join: %v", err)
	}
	if !isMember {
		t.Fatal("user is not a member after join")
	}

	// Step 3: User sends leave request
	leaveEv := event.New()
	leaveEv.Kind = nip43.KindLeaveRequest
	copy(leaveEv.Pubkey, userPubkey)
	leaveEv.Tags = tag.NewS()
	leaveEv.Tags.Append(tag.NewFromAny("-"))
	leaveEv.CreatedAt = time.Now().Unix()
	leaveEv.Content = []byte("")
	if err = leaveEv.Sign(userSigner); err != nil {
		t.Fatalf("failed to sign leave event: %v", err)
	}

	err = listener.HandleNIP43LeaveRequest(leaveEv)
	if err != nil {
		t.Fatalf("failed to handle leave request: %v", err)
	}

	// Verify user is no longer member
	isMember, err = db.IsNIP43Member(userPubkey)
	if err != nil {
		t.Fatalf("failed to check membership after leave: %v", err)
	}
	if isMember {
		t.Fatal("user is still a member after leave")
	}
}

// TestMultipleUsersJoining tests multiple users joining concurrently
func TestMultipleUsersJoining(t *testing.T) {
	listener, db, cleanup := setupTestListener(t)
	defer cleanup()

	userCount := 10
	done := make(chan bool, userCount)

	for i := 0; i < userCount; i++ {
		go func(index int) {
			// Generate user
			userSecret, err := keys.GenerateSecretKey()
			if err != nil {
				t.Errorf("failed to generate user secret %d: %v", index, err)
				done <- false
				return
			}
			userSigner, err := p8k.New()
			if err != nil {
				t.Errorf("failed to create signer %d: %v", index, err)
				done <- false
				return
			}
			if err = userSigner.InitSec(userSecret); err != nil {
				t.Errorf("failed to initialize signer %d: %v", index, err)
				done <- false
				return
			}
			userPubkey := userSigner.Pub()

			// Generate invite code
			code, err := listener.Server.InviteManager.GenerateCode()
			if err != nil {
				t.Errorf("failed to generate invite code %d: %v", index, err)
				done <- false
				return
			}

			// Create join request
			joinEv := event.New()
			joinEv.Kind = nip43.KindJoinRequest
			copy(joinEv.Pubkey, userPubkey)
			joinEv.Tags = tag.NewS()
			joinEv.Tags.Append(tag.NewFromAny("-"))
			joinEv.Tags.Append(tag.NewFromAny("claim", code))
			joinEv.CreatedAt = time.Now().Unix()
			joinEv.Content = []byte("")
			if err = joinEv.Sign(userSigner); err != nil {
				t.Errorf("failed to sign event %d: %v", index, err)
				done <- false
				return
			}

			// Handle join request
			if err = listener.HandleNIP43JoinRequest(joinEv); err != nil {
				t.Errorf("failed to handle join request %d: %v", index, err)
				done <- false
				return
			}

			done <- true
		}(i)
	}

	// Wait for all goroutines
	successCount := 0
	for i := 0; i < userCount; i++ {
		if <-done {
			successCount++
		}
	}

	if successCount != userCount {
		t.Errorf("not all users joined successfully: %d/%d", successCount, userCount)
	}

	// Verify member count
	members, err := db.GetAllNIP43Members()
	if err != nil {
		t.Fatalf("failed to get all members: %v", err)
	}

	if len(members) != successCount {
		t.Errorf("wrong member count: got %d, want %d", len(members), successCount)
	}
}
