package rpc

import (
	"context"
	"encoding/json"
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	sqlitestorage "github.com/steveyegge/beads/internal/storage/sqlite"
	"github.com/steveyegge/beads/internal/types"
)

// TestStressConcurrentAgents tests 4+ concurrent agents creating issues
func TestStressConcurrentAgents(t *testing.T) {
	server, _, cleanup := setupTestServer(t)
	defer cleanup()

	socketPath := server.socketPath
	numAgents := 8
	issuesPerAgent := 100

	var wg sync.WaitGroup
	errors := make(chan error, numAgents)
	successCount := int32(0)

	for i := 0; i < numAgents; i++ {
		wg.Add(1)
		go func(agentID int) {
			defer wg.Done()

			client, err := TryConnect(socketPath)
			if err != nil {
				errors <- fmt.Errorf("agent %d: failed to connect: %w", agentID, err)
				return
			}
			defer client.Close()

			for j := 0; j < issuesPerAgent; j++ {
				args := &CreateArgs{
					Title:       fmt.Sprintf("Agent %d Issue %d", agentID, j),
					Description: fmt.Sprintf("Created by agent %d", agentID),
					IssueType:   "task",
					Priority:    2,
				}

				if _, err := client.Create(args); err != nil {
					errors <- fmt.Errorf("agent %d issue %d: %w", agentID, j, err)
					return
				}
				atomic.AddInt32(&successCount, 1)
			}
		}(i)
	}

	wg.Wait()
	close(errors)

	for err := range errors {
		t.Errorf("Concurrent agent error: %v", err)
	}

	expectedCount := int32(numAgents * issuesPerAgent)
	if successCount != expectedCount {
		t.Errorf("Expected %d successful creates, got %d", expectedCount, successCount)
	}
}

// TestStressBatchOperations tests batch operations under load
func TestStressBatchOperations(t *testing.T) {
	server, client, cleanup := setupTestServer(t)
	defer cleanup()

	createArgs1 := &CreateArgs{
		Title:     "Batch Issue 1",
		IssueType: "task",
		Priority:  1,
	}
	createArgs2 := &CreateArgs{
		Title:     "Batch Issue 2",
		IssueType: "task",
		Priority:  2,
	}

	createArgs1JSON, _ := json.Marshal(createArgs1)
	createArgs2JSON, _ := json.Marshal(createArgs2)

	batchArgs := &BatchArgs{
		Operations: []BatchOperation{
			{Operation: OpCreate, Args: createArgs1JSON},
			{Operation: OpCreate, Args: createArgs2JSON},
		},
	}

	resp, err := client.Batch(batchArgs)
	if err != nil {
		t.Fatalf("Batch failed: %v", err)
	}

	var batchResp BatchResponse
	if err := json.Unmarshal(resp.Data, &batchResp); err != nil {
		t.Fatalf("Failed to unmarshal batch response: %v", err)
	}

	if len(batchResp.Results) != 2 {
		t.Errorf("Expected 2 results, got %d", len(batchResp.Results))
	}

	for i, result := range batchResp.Results {
		if !result.Success {
			t.Errorf("Operation %d failed: %s", i, result.Error)
		}
	}

	socketPath := server.socketPath
	numAgents := 4
	batchesPerAgent := 50

	var wg sync.WaitGroup
	errors := make(chan error, numAgents)

	for i := 0; i < numAgents; i++ {
		wg.Add(1)
		go func(agentID int) {
			defer wg.Done()

			client, err := TryConnect(socketPath)
			if err != nil {
				errors <- fmt.Errorf("agent %d: failed to connect: %w", agentID, err)
				return
			}
			defer client.Close()

			for j := 0; j < batchesPerAgent; j++ {
				createArgs1 := &CreateArgs{
					Title:     fmt.Sprintf("Agent %d Batch %d Issue 1", agentID, j),
					IssueType: "task",
					Priority:  1,
				}
				createArgs2 := &CreateArgs{
					Title:     fmt.Sprintf("Agent %d Batch %d Issue 2", agentID, j),
					IssueType: "bug",
					Priority:  0,
				}

				createArgs1JSON, _ := json.Marshal(createArgs1)
				createArgs2JSON, _ := json.Marshal(createArgs2)

				batchArgs := &BatchArgs{
					Operations: []BatchOperation{
						{Operation: OpCreate, Args: createArgs1JSON},
						{Operation: OpCreate, Args: createArgs2JSON},
					},
				}

				if _, err := client.Batch(batchArgs); err != nil {
					errors <- fmt.Errorf("agent %d batch %d: %w", agentID, j, err)
					return
				}
			}
		}(i)
	}

	wg.Wait()
	close(errors)

	for err := range errors {
		t.Errorf("Batch stress error: %v", err)
	}
}

// TestStressMixedOperations tests concurrent mixed operations
func TestStressMixedOperations(t *testing.T) {
	server, _, cleanup := setupTestServer(t)
	defer cleanup()

	socketPath := server.socketPath
	numAgents := 6
	opsPerAgent := 50

	setupClient, err := TryConnect(socketPath)
	if err != nil {
		t.Fatalf("Failed to connect: %v", err)
	}
	defer setupClient.Close()

	baseIssues := make([]string, 10)
	for i := 0; i < 10; i++ {
		args := &CreateArgs{
			Title:     fmt.Sprintf("Base Issue %d", i),
			IssueType: "task",
			Priority:  2,
		}
		resp, err := setupClient.Create(args)
		if err != nil {
			t.Fatalf("Failed to create base issue: %v", err)
		}
		var issue types.Issue
		json.Unmarshal(resp.Data, &issue)
		baseIssues[i] = issue.ID
	}

	var wg sync.WaitGroup
	errors := make(chan error, numAgents)

	for i := 0; i < numAgents; i++ {
		wg.Add(1)
		go func(agentID int) {
			defer wg.Done()

			client, err := TryConnect(socketPath)
			if err != nil {
				errors <- fmt.Errorf("agent %d: failed to connect: %w", agentID, err)
				return
			}
			defer client.Close()

			for j := 0; j < opsPerAgent; j++ {
				opType := j % 5

				switch opType {
				case 0:
					args := &CreateArgs{
						Title:     fmt.Sprintf("Agent %d New Issue %d", agentID, j),
						IssueType: "task",
						Priority:  2,
					}
					if _, err := client.Create(args); err != nil {
						errors <- fmt.Errorf("agent %d create: %w", agentID, err)
						return
					}

				case 1:
					issueID := baseIssues[j%len(baseIssues)]
					newTitle := fmt.Sprintf("Updated by agent %d", agentID)
					args := &UpdateArgs{
						ID:    issueID,
						Title: &newTitle,
					}
					if _, err := client.Update(args); err != nil {
						errors <- fmt.Errorf("agent %d update: %w", agentID, err)
						return
					}

				case 2:
					issueID := baseIssues[j%len(baseIssues)]
					args := &ShowArgs{ID: issueID}
					if _, err := client.Show(args); err != nil {
						errors <- fmt.Errorf("agent %d show: %w", agentID, err)
						return
					}

				case 3:
					args := &ListArgs{Limit: 10}
					if _, err := client.List(args); err != nil {
						errors <- fmt.Errorf("agent %d list: %w", agentID, err)
						return
					}

				case 4:
					args := &ReadyArgs{Limit: 5}
					if _, err := client.Ready(args); err != nil {
						errors <- fmt.Errorf("agent %d ready: %w", agentID, err)
						return
					}
				}
			}
		}(i)
	}

	wg.Wait()
	close(errors)

	for err := range errors {
		t.Errorf("Mixed operations error: %v", err)
	}
}

// TestStressTimeouts tests timeout handling
func TestStressTimeouts(t *testing.T) {
	_, client, cleanup := setupTestServer(t)
	defer cleanup()

	client.SetTimeout(5 * time.Second)

	args := &CreateArgs{
		Title:     "Timeout Test",
		IssueType: "task",
		Priority:  2,
	}

	if _, err := client.Create(args); err != nil {
		t.Fatalf("Create with timeout failed: %v", err)
	}

	client.SetTimeout(1 * time.Nanosecond)
	if _, err := client.Create(args); err == nil {
		t.Error("Expected timeout error, got success")
	}
}

// TestStressNoUniqueConstraintViolations verifies no ID collisions
func TestStressNoUniqueConstraintViolations(t *testing.T) {
	tmpDir, err := os.MkdirTemp("", "bd-stress-unique-*")
	if err != nil {
		t.Fatalf("Failed to create temp dir: %v", err)
	}
	defer os.RemoveAll(tmpDir)

	dbPath := filepath.Join(tmpDir, "test.db")
	socketPath := filepath.Join(tmpDir, "bd.sock")

	store, err := sqlitestorage.New(dbPath)
	if err != nil {
		t.Fatalf("Failed to create store: %v", err)
	}

	server := NewServer(socketPath, store)

	ctx, cancel := context.WithCancel(context.Background())
	go func() {
		if err := server.Start(ctx); err != nil {
			t.Logf("Server error: %v", err)
		}
	}()

	time.Sleep(100 * time.Millisecond)

	defer func() {
		cancel()
		server.Stop()
		store.Close()
	}()

	numAgents := 10
	issuesPerAgent := 100

	var wg sync.WaitGroup
	errors := make(chan error, numAgents)
	issueIDs := make(chan string, numAgents*issuesPerAgent)

	for i := 0; i < numAgents; i++ {
		wg.Add(1)
		go func(agentID int) {
			defer wg.Done()

			client, err := TryConnect(socketPath)
			if err != nil {
				errors <- fmt.Errorf("agent %d: failed to connect: %w", agentID, err)
				return
			}
			defer client.Close()

			for j := 0; j < issuesPerAgent; j++ {
				args := &CreateArgs{
					Title:     fmt.Sprintf("Agent %d Issue %d", agentID, j),
					IssueType: "task",
					Priority:  2,
				}

				resp, err := client.Create(args)
				if err != nil {
					errors <- fmt.Errorf("agent %d issue %d: %w", agentID, j, err)
					return
				}

				var issue types.Issue
				if err := json.Unmarshal(resp.Data, &issue); err != nil {
					errors <- fmt.Errorf("agent %d unmarshal: %w", agentID, err)
					return
				}

				issueIDs <- issue.ID
			}
		}(i)
	}

	wg.Wait()
	close(errors)
	close(issueIDs)

	for err := range errors {
		t.Errorf("Unique constraint test error: %v", err)
	}

	idSet := make(map[string]bool)
	duplicates := []string{}

	for id := range issueIDs {
		if idSet[id] {
			duplicates = append(duplicates, id)
		}
		idSet[id] = true
	}

	if len(duplicates) > 0 {
		t.Errorf("Found %d duplicate IDs: %v", len(duplicates), duplicates)
	}

	expectedCount := numAgents * issuesPerAgent
	if len(idSet) != expectedCount {
		t.Errorf("Expected %d unique IDs, got %d", expectedCount, len(idSet))
	}
}
