package nip43

import (
	"testing"
	"time"

	"git.mleku.dev/mleku/nostr/crypto/keys"
	"git.mleku.dev/mleku/nostr/encoders/event"
	"git.mleku.dev/mleku/nostr/encoders/tag"
)

// TestInviteManager_GenerateCode tests invite code generation
func TestInviteManager_GenerateCode(t *testing.T) {
	im := NewInviteManager(24 * time.Hour)

	code, err := im.GenerateCode()
	if err != nil {
		t.Fatalf("failed to generate code: %v", err)
	}

	if code == "" {
		t.Fatal("generated code is empty")
	}

	// Verify the code exists in the manager
	im.mu.Lock()
	invite, exists := im.codes[code]
	im.mu.Unlock()

	if !exists {
		t.Fatal("generated code not found in manager")
	}

	if invite.Code != code {
		t.Errorf("code mismatch: got %s, want %s", invite.Code, code)
	}

	if invite.UsedBy != nil {
		t.Error("newly generated code should not be used")
	}

	if time.Until(invite.ExpiresAt) > 24*time.Hour {
		t.Error("expiry time is too far in the future")
	}
}

// TestInviteManager_ValidateAndConsume tests invite code validation
func TestInviteManager_ValidateAndConsume(t *testing.T) {
	im := NewInviteManager(24 * time.Hour)

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

	testPubkey := make([]byte, 32)
	for i := range testPubkey {
		testPubkey[i] = byte(i)
	}

	// Test valid code
	valid, reason := im.ValidateAndConsume(code, testPubkey)
	if !valid {
		t.Fatalf("valid code rejected: %s", reason)
	}

	// Test already used code
	valid, reason = im.ValidateAndConsume(code, testPubkey)
	if valid {
		t.Error("already used code was accepted")
	}
	if reason != "invite code already used" {
		t.Errorf("wrong rejection reason: got %s", reason)
	}

	// Test invalid code
	valid, reason = im.ValidateAndConsume("invalid-code", testPubkey)
	if valid {
		t.Error("invalid code was accepted")
	}
	if reason != "invalid invite code" {
		t.Errorf("wrong rejection reason: got %s", reason)
	}
}

// TestInviteManager_ExpiredCode tests expired invite code handling
func TestInviteManager_ExpiredCode(t *testing.T) {
	// Create manager with very short expiry
	im := NewInviteManager(1 * time.Millisecond)

	code, err := im.GenerateCode()
	if err != nil {
		t.Fatalf("failed to generate code: %v", err)
	}

	// Wait for expiry
	time.Sleep(10 * time.Millisecond)

	testPubkey := make([]byte, 32)
	valid, reason := im.ValidateAndConsume(code, testPubkey)
	if valid {
		t.Error("expired code was accepted")
	}
	if reason != "invite code expired" {
		t.Errorf("wrong rejection reason: got %s, want 'invite code expired'", reason)
	}

	// Verify code was deleted
	im.mu.Lock()
	_, exists := im.codes[code]
	im.mu.Unlock()

	if exists {
		t.Error("expired code was not deleted")
	}
}

// TestInviteManager_CleanupExpired tests cleanup of expired codes
func TestInviteManager_CleanupExpired(t *testing.T) {
	im := NewInviteManager(1 * time.Millisecond)

	// Generate multiple codes
	codes := make([]string, 5)
	for i := 0; i < 5; i++ {
		code, err := im.GenerateCode()
		if err != nil {
			t.Fatalf("failed to generate code %d: %v", i, err)
		}
		codes[i] = code
	}

	// Wait for expiry
	time.Sleep(10 * time.Millisecond)

	// Cleanup
	im.CleanupExpired()

	// Verify all codes were deleted
	im.mu.Lock()
	remaining := len(im.codes)
	im.mu.Unlock()

	if remaining != 0 {
		t.Errorf("cleanup failed: %d codes remaining", remaining)
	}
}

// TestBuildMemberListEvent tests membership list event creation
func TestBuildMemberListEvent(t *testing.T) {
	// Generate a test relay secret
	relaySecret, err := keys.GenerateSecretKey()
	if err != nil {
		t.Fatalf("failed to generate relay secret: %v", err)
	}

	// Create test member pubkeys
	members := make([][]byte, 3)
	for i := 0; i < 3; i++ {
		members[i] = make([]byte, 32)
		for j := range members[i] {
			members[i][j] = byte(i*10 + j)
		}
	}

	// Build event
	ev, err := BuildMemberListEvent(relaySecret, members)
	if err != nil {
		t.Fatalf("failed to build member list event: %v", err)
	}

	// Verify event kind
	if ev.Kind != KindMemberList {
		t.Errorf("wrong event kind: got %d, want %d", ev.Kind, KindMemberList)
	}

	// Verify NIP-70 tag
	minusTag := ev.Tags.GetFirst([]byte("-"))
	if minusTag == nil {
		t.Error("missing NIP-70 `-` tag")
	}

	// Verify member tags
	memberTags := ev.Tags.GetAll([]byte("member"))
	if len(memberTags) != 3 {
		t.Errorf("wrong number of member tags: got %d, want 3", len(memberTags))
	}

	// Verify signature
	valid, err := ev.Verify()
	if err != nil {
		t.Fatalf("signature verification error: %v", err)
	}
	if !valid {
		t.Error("event signature is invalid")
	}
}

// TestBuildAddUserEvent tests add user event creation
func TestBuildAddUserEvent(t *testing.T) {
	relaySecret, err := keys.GenerateSecretKey()
	if err != nil {
		t.Fatalf("failed to generate relay secret: %v", err)
	}

	userPubkey := make([]byte, 32)
	for i := range userPubkey {
		userPubkey[i] = byte(i)
	}

	ev, err := BuildAddUserEvent(relaySecret, userPubkey)
	if err != nil {
		t.Fatalf("failed to build add user event: %v", err)
	}

	// Verify event kind
	if ev.Kind != KindAddUser {
		t.Errorf("wrong event kind: got %d, want %d", ev.Kind, KindAddUser)
	}

	// Verify NIP-70 tag
	minusTag := ev.Tags.GetFirst([]byte("-"))
	if minusTag == nil {
		t.Error("missing NIP-70 `-` tag")
	}

	// Verify p tag
	pTag := ev.Tags.GetFirst([]byte("p"))
	if pTag == nil {
		t.Error("missing p tag")
	}

	// Verify signature
	valid, err := ev.Verify()
	if err != nil {
		t.Fatalf("signature verification error: %v", err)
	}
	if !valid {
		t.Error("event signature is invalid")
	}
}

// TestBuildRemoveUserEvent tests remove user event creation
func TestBuildRemoveUserEvent(t *testing.T) {
	relaySecret, err := keys.GenerateSecretKey()
	if err != nil {
		t.Fatalf("failed to generate relay secret: %v", err)
	}

	userPubkey := make([]byte, 32)
	for i := range userPubkey {
		userPubkey[i] = byte(i)
	}

	ev, err := BuildRemoveUserEvent(relaySecret, userPubkey)
	if err != nil {
		t.Fatalf("failed to build remove user event: %v", err)
	}

	// Verify event kind
	if ev.Kind != KindRemoveUser {
		t.Errorf("wrong event kind: got %d, want %d", ev.Kind, KindRemoveUser)
	}

	// Verify NIP-70 tag
	minusTag := ev.Tags.GetFirst([]byte("-"))
	if minusTag == nil {
		t.Error("missing NIP-70 `-` tag")
	}

	// Verify p tag
	pTag := ev.Tags.GetFirst([]byte("p"))
	if pTag == nil {
		t.Error("missing p tag")
	}

	// Verify signature
	valid, err := ev.Verify()
	if err != nil {
		t.Fatalf("signature verification error: %v", err)
	}
	if !valid {
		t.Error("event signature is invalid")
	}
}

// TestBuildInviteEvent tests invite event creation
func TestBuildInviteEvent(t *testing.T) {
	relaySecret, err := keys.GenerateSecretKey()
	if err != nil {
		t.Fatalf("failed to generate relay secret: %v", err)
	}

	inviteCode := "test-invite-code-12345"

	ev, err := BuildInviteEvent(relaySecret, inviteCode)
	if err != nil {
		t.Fatalf("failed to build invite event: %v", err)
	}

	// Verify event kind
	if ev.Kind != KindInviteReq {
		t.Errorf("wrong event kind: got %d, want %d", ev.Kind, KindInviteReq)
	}

	// Verify NIP-70 tag
	minusTag := ev.Tags.GetFirst([]byte("-"))
	if minusTag == nil {
		t.Error("missing NIP-70 `-` tag")
	}

	// Verify claim tag
	claimTag := ev.Tags.GetFirst([]byte("claim"))
	if claimTag == nil {
		t.Error("missing claim tag")
	}
	if claimTag.Len() < 2 {
		t.Error("claim tag has no value")
	}
	if string(claimTag.T[1]) != inviteCode {
		t.Errorf("wrong invite code in tag: got %s, want %s", string(claimTag.T[1]), inviteCode)
	}

	// Verify signature
	valid, err := ev.Verify()
	if err != nil {
		t.Fatalf("signature verification error: %v", err)
	}
	if !valid {
		t.Error("event signature is invalid")
	}
}

// TestValidateJoinRequest tests join request validation
func TestValidateJoinRequest(t *testing.T) {
	tests := []struct {
		name          string
		setupEvent    func() *event.E
		expectValid   bool
		expectCode    string
		expectReason  string
	}{
		{
			name: "valid join request",
			setupEvent: func() *event.E {
				ev := event.New()
				ev.Kind = KindJoinRequest
				ev.Tags = tag.NewS()
				ev.Tags.Append(tag.NewFromAny("-"))
				ev.Tags.Append(tag.NewFromAny("claim", "test-code-123"))
				ev.CreatedAt = time.Now().Unix()
				return ev
			},
			expectValid:  true,
			expectCode:   "test-code-123",
			expectReason: "",
		},
		{
			name: "wrong kind",
			setupEvent: func() *event.E {
				ev := event.New()
				ev.Kind = 1000
				return ev
			},
			expectValid:  false,
			expectReason: "invalid event kind",
		},
		{
			name: "missing minus tag",
			setupEvent: func() *event.E {
				ev := event.New()
				ev.Kind = KindJoinRequest
				ev.Tags = tag.NewS()
				ev.Tags.Append(tag.NewFromAny("claim", "test-code"))
				ev.CreatedAt = time.Now().Unix()
				return ev
			},
			expectValid:  false,
			expectReason: "missing NIP-70 `-` tag",
		},
		{
			name: "missing claim tag",
			setupEvent: func() *event.E {
				ev := event.New()
				ev.Kind = KindJoinRequest
				ev.Tags = tag.NewS()
				ev.Tags.Append(tag.NewFromAny("-"))
				ev.CreatedAt = time.Now().Unix()
				return ev
			},
			expectValid:  false,
			expectReason: "missing claim tag",
		},
		{
			name: "timestamp too old",
			setupEvent: func() *event.E {
				ev := event.New()
				ev.Kind = KindJoinRequest
				ev.Tags = tag.NewS()
				ev.Tags.Append(tag.NewFromAny("-"))
				ev.Tags.Append(tag.NewFromAny("claim", "test-code"))
				ev.CreatedAt = time.Now().Unix() - 700 // More than 10 minutes ago
				return ev
			},
			expectValid:  false,
			expectCode:   "test-code",
			expectReason: "timestamp out of range",
		},
		{
			name: "timestamp too far in future",
			setupEvent: func() *event.E {
				ev := event.New()
				ev.Kind = KindJoinRequest
				ev.Tags = tag.NewS()
				ev.Tags.Append(tag.NewFromAny("-"))
				ev.Tags.Append(tag.NewFromAny("claim", "test-code"))
				ev.CreatedAt = time.Now().Unix() + 700 // More than 10 minutes ahead
				return ev
			},
			expectValid:  false,
			expectCode:   "test-code",
			expectReason: "timestamp out of range",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ev := tt.setupEvent()
			code, valid, reason := ValidateJoinRequest(ev)

			if valid != tt.expectValid {
				t.Errorf("valid mismatch: got %v, want %v", valid, tt.expectValid)
			}
			if tt.expectCode != "" && code != tt.expectCode {
				t.Errorf("code mismatch: got %s, want %s", code, tt.expectCode)
			}
			if tt.expectReason != "" && reason != tt.expectReason {
				t.Errorf("reason mismatch: got %s, want %s", reason, tt.expectReason)
			}
		})
	}
}

// TestValidateLeaveRequest tests leave request validation
func TestValidateLeaveRequest(t *testing.T) {
	tests := []struct {
		name         string
		setupEvent   func() *event.E
		expectValid  bool
		expectReason string
	}{
		{
			name: "valid leave request",
			setupEvent: func() *event.E {
				ev := event.New()
				ev.Kind = KindLeaveRequest
				ev.Tags = tag.NewS()
				ev.Tags.Append(tag.NewFromAny("-"))
				ev.CreatedAt = time.Now().Unix()
				return ev
			},
			expectValid:  true,
			expectReason: "",
		},
		{
			name: "wrong kind",
			setupEvent: func() *event.E {
				ev := event.New()
				ev.Kind = 1000
				return ev
			},
			expectValid:  false,
			expectReason: "invalid event kind",
		},
		{
			name: "missing minus tag",
			setupEvent: func() *event.E {
				ev := event.New()
				ev.Kind = KindLeaveRequest
				ev.CreatedAt = time.Now().Unix()
				return ev
			},
			expectValid:  false,
			expectReason: "missing NIP-70 `-` tag",
		},
		{
			name: "timestamp out of range",
			setupEvent: func() *event.E {
				ev := event.New()
				ev.Kind = KindLeaveRequest
				ev.Tags = tag.NewS()
				ev.Tags.Append(tag.NewFromAny("-"))
				ev.CreatedAt = time.Now().Unix() - 700
				return ev
			},
			expectValid:  false,
			expectReason: "timestamp out of range",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			ev := tt.setupEvent()
			valid, reason := ValidateLeaveRequest(ev)

			if valid != tt.expectValid {
				t.Errorf("valid mismatch: got %v, want %v", valid, tt.expectValid)
			}
			if tt.expectReason != "" && reason != tt.expectReason {
				t.Errorf("reason mismatch: got %s, want %s", reason, tt.expectReason)
			}
		})
	}
}
