// Integration Tests for Issue 009: UCXI + DHT Encryption + Search
// These tests validate the complete integration between UCXI HTTP server,
// encrypted DHT storage, and search functionality with proper UCXL addressing.

package integration

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"strings"
	"testing"
	"time"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	"chorus.services/bzzz/pkg/config"
	"chorus.services/bzzz/pkg/crypto"
	"chorus.services/bzzz/pkg/dht"
	"chorus.services/bzzz/pkg/ucxi"
	"chorus.services/bzzz/pkg/ucxl"
)

// UCXIDHTIntegrationTestSuite provides comprehensive testing for UCXI + DHT + Encryption
type UCXIDHTIntegrationTestSuite struct {
	ctx           context.Context
	config        *config.Config
	keyManager    *crypto.KeyManager
	dhtStorage    dht.DHT
	ucxiServer    *ucxi.Server
	httpServer    *httptest.Server
	testData      map[string][]byte
	testAddresses []string
}

func TestUCXIDHTIntegration(t *testing.T) {
	suite := NewUCXIDHTIntegrationTestSuite(t)
	defer suite.Cleanup()

	t.Run("PutGetDelete_ValidAddresses", suite.TestPutGetDeleteValidAddresses)
	t.Run("Encryption_Decryption_RoleBased", suite.TestEncryptionDecryptionRoleBased)
	t.Run("Search_AgentRoleProjectTaskFilters", suite.TestSearchWithFilters)
	t.Run("TemporalAddressing_Navigation", suite.TestTemporalAddressing)
	t.Run("InvalidAddress_Returns_UCXL400", suite.TestInvalidAddressValidation)
	t.Run("ConcurrentOperations_ThreadSafety", suite.TestConcurrentOperations)
	t.Run("LargePayload_StorageRetrieval", suite.TestLargePayloadHandling)
	t.Run("TTL_Expiration_Cleanup", suite.TestTTLExpirationCleanup)
}

func NewUCXIDHTIntegrationTestSuite(t *testing.T) *UCXIDHTIntegrationTestSuite {
	ctx := context.Background()

	// Initialize test configuration
	cfg := &config.Config{
		Security: config.SecurityConfig{
			AuditLogging:     true,
			KeyRotationDays:  30,
			MaxKeyAge:        time.Hour * 24 * 365,
			RequireKeyEscrow: true,
		},
		Roles: []config.Role{
			{Name: "developer", Permissions: []string{"read", "write"}},
			{Name: "admin", Permissions: []string{"read", "write", "delete", "admin"}},
			{Name: "viewer", Permissions: []string{"read"}},
		},
	}

	// Initialize key manager
	keyManager, err := crypto.NewKeyManager(cfg, crypto.NewInMemoryKeyStore())
	require.NoError(t, err, "Failed to create key manager")

	// Initialize mock DHT storage
	dhtStorage := dht.NewMockDHT()

	// Initialize encrypted storage layer
	encryptedStorage, err := dht.NewEncryptedStorage(dhtStorage, keyManager)
	require.NoError(t, err, "Failed to create encrypted storage")

	// Initialize UCXI server
	ucxiServer, err := ucxi.NewServer(encryptedStorage)
	require.NoError(t, err, "Failed to create UCXI server")

	// Create HTTP test server
	httpServer := httptest.NewServer(ucxiServer)

	// Prepare test data
	testData := map[string][]byte{
		"simple_config":    []byte(`{"version": "1.0", "enabled": true}`),
		"user_data":       []byte(`{"name": "John Doe", "role": "developer", "team": "backend"}`),
		"large_document":  bytes.Repeat([]byte("test data "), 1000),
		"json_array":      []byte(`[{"id": 1, "value": "first"}, {"id": 2, "value": "second"}]`),
		"binary_data":     []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64},
	}

	// Generate test addresses with different patterns
	testAddresses := []string{
		"ucxl://agent1:developer@project1:task1/*^",
		"ucxl://admin:admin@bzzz:config/cluster/nodes*^",
		"ucxl://user1:viewer@docs:read/api/v1*^",
		"ucxl://service:developer@microservice:deploy/staging*^",
		"ucxl://monitor:admin@system:health/metrics*^",
	}

	return &UCXIDHTIntegrationTestSuite{
		ctx:           ctx,
		config:        cfg,
		keyManager:    keyManager,
		dhtStorage:    dhtStorage,
		ucxiServer:    ucxiServer,
		httpServer:    httpServer,
		testData:      testData,
		testAddresses: testAddresses,
	}
}

func (suite *UCXIDHTIntegrationTestSuite) Cleanup() {
	suite.httpServer.Close()
}

// TestPutGetDeleteValidAddresses tests the complete PUT/GET/DELETE cycle with valid UCXL addresses
func (suite *UCXIDHTIntegrationTestSuite) TestPutGetDeleteValidAddresses(t *testing.T) {
	for i, address := range suite.testAddresses {
		testDataKey := []string{"simple_config", "user_data", "json_array", "binary_data", "large_document"}[i%5]
		testData := suite.testData[testDataKey]

		t.Run(fmt.Sprintf("Address_%d_%s", i, strings.ReplaceAll(address, ":", "_")), func(t *testing.T) {
			// 1. PUT: Store data at the address
			putResp, err := http.Post(
				fmt.Sprintf("%s/put/%s", suite.httpServer.URL, address),
				"application/octet-stream",
				bytes.NewReader(testData),
			)
			require.NoError(t, err, "PUT request failed")
			require.Equal(t, http.StatusOK, putResp.StatusCode, "PUT should succeed")
			putResp.Body.Close()

			// 2. GET: Retrieve data from the address
			getResp, err := http.Get(fmt.Sprintf("%s/get/%s", suite.httpServer.URL, address))
			require.NoError(t, err, "GET request failed")
			require.Equal(t, http.StatusOK, getResp.StatusCode, "GET should succeed")

			var getBody bytes.Buffer
			_, err = getBody.ReadFrom(getResp.Body)
			require.NoError(t, err, "Failed to read GET response body")
			getResp.Body.Close()

			assert.Equal(t, testData, getBody.Bytes(), "Retrieved data should match stored data")

			// 3. DELETE: Remove data from the address
			delReq, err := http.NewRequest("DELETE", fmt.Sprintf("%s/delete/%s", suite.httpServer.URL, address), nil)
			require.NoError(t, err, "Failed to create DELETE request")

			client := &http.Client{}
			delResp, err := client.Do(delReq)
			require.NoError(t, err, "DELETE request failed")
			require.Equal(t, http.StatusOK, delResp.StatusCode, "DELETE should succeed")
			delResp.Body.Close()

			// 4. GET after DELETE should return 404
			getAfterDelResp, err := http.Get(fmt.Sprintf("%s/get/%s", suite.httpServer.URL, address))
			require.NoError(t, err, "GET after DELETE request failed")
			assert.Equal(t, http.StatusNotFound, getAfterDelResp.StatusCode, "GET after DELETE should return 404")
			getAfterDelResp.Body.Close()
		})
	}
}

// TestEncryptionDecryptionRoleBased tests role-based encryption and decryption
func (suite *UCXIDHTIntegrationTestSuite) TestEncryptionDecryptionRoleBased(t *testing.T) {
	testCases := []struct {
		name        string
		address     string
		role        string
		data        []byte
		expectError bool
	}{
		{
			name:        "Developer_ReadWrite",
			address:     "ucxl://dev1:developer@project1:task1/*^",
			role:        "developer",
			data:        []byte(`{"secret": "developer_data", "level": "standard"}`),
			expectError: false,
		},
		{
			name:        "Admin_FullAccess",
			address:     "ucxl://admin1:admin@system:config/*^",
			role:        "admin",
			data:        []byte(`{"secret": "admin_data", "level": "restricted"}`),
			expectError: false,
		},
		{
			name:        "Viewer_ReadOnly",
			address:     "ucxl://viewer1:viewer@docs:read/*^",
			role:        "viewer",
			data:        []byte(`{"public": "viewer_data", "level": "public"}`),
			expectError: false,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			// Parse address to extract role information
			parsedAddr, err := ucxl.ParseUCXLAddress(tc.address)
			require.NoError(t, err, "Failed to parse test address")
			assert.Equal(t, tc.role, parsedAddr.Role, "Role should match expected value")

			// Store encrypted data
			putResp, err := http.Post(
				fmt.Sprintf("%s/put/%s", suite.httpServer.URL, tc.address),
				"application/json",
				bytes.NewReader(tc.data),
			)
			require.NoError(t, err, "PUT request failed")

			if tc.expectError {
				assert.NotEqual(t, http.StatusOK, putResp.StatusCode, "PUT should fail for invalid role")
				putResp.Body.Close()
				return
			}

			require.Equal(t, http.StatusOK, putResp.StatusCode, "PUT should succeed for valid role")
			putResp.Body.Close()

			// Retrieve and verify decrypted data
			getResp, err := http.Get(fmt.Sprintf("%s/get/%s", suite.httpServer.URL, tc.address))
			require.NoError(t, err, "GET request failed")
			require.Equal(t, http.StatusOK, getResp.StatusCode, "GET should succeed")

			var retrieved []byte
			var getBody bytes.Buffer
			_, err = getBody.ReadFrom(getResp.Body)
			require.NoError(t, err, "Failed to read response")
			retrieved = getBody.Bytes()
			getResp.Body.Close()

			assert.Equal(t, tc.data, retrieved, "Decrypted data should match original")

			// Verify data is actually encrypted in storage
			directValue, err := suite.dhtStorage.GetValue(suite.ctx, tc.address)
			if err == nil {
				// Direct storage value should be different from original (encrypted)
				assert.NotEqual(t, tc.data, directValue, "Data should be encrypted in storage")
			}
		})
	}
}

// TestSearchWithFilters tests search functionality with agent/role/project/task filters
func (suite *UCXIDHTIntegrationTestSuite) TestSearchWithFilters(t *testing.T) {
	// First, populate storage with multiple entries for searching
	testEntries := []struct {
		address string
		data    []byte
	}{
		{"ucxl://alice:developer@projectA:feature1/*^", []byte(`{"author": "alice", "type": "feature"}`)},
		{"ucxl://bob:developer@projectA:bugfix2/*^", []byte(`{"author": "bob", "type": "bugfix"}`)},
		{"ucxl://charlie:admin@projectB:config3/*^", []byte(`{"author": "charlie", "type": "config"}`)},
		{"ucxl://alice:developer@projectB:feature4/*^", []byte(`{"author": "alice", "type": "feature"}`)},
		{"ucxl://diana:viewer@projectA:read5/*^", []byte(`{"author": "diana", "type": "read"}`)},
	}

	// Store all test entries
	for _, entry := range testEntries {
		putResp, err := http.Post(
			fmt.Sprintf("%s/put/%s", suite.httpServer.URL, entry.address),
			"application/json",
			bytes.NewReader(entry.data),
		)
		require.NoError(t, err, "Failed to store test entry")
		require.Equal(t, http.StatusOK, putResp.StatusCode, "PUT should succeed")
		putResp.Body.Close()
	}

	searchTestCases := []struct {
		name           string
		searchPattern  string
		expectedCount  int
		expectedAgents []string
	}{
		{
			name:           "Search_All_ProjectA",
			searchPattern:  "ucxl://*:*@projectA:*/*",
			expectedCount:  3,
			expectedAgents: []string{"alice", "bob", "diana"},
		},
		{
			name:           "Search_Developer_Role",
			searchPattern:  "ucxl://*:developer@*:*/*",
			expectedCount:  3,
			expectedAgents: []string{"alice", "bob", "alice"}, // alice appears twice
		},
		{
			name:           "Search_Alice_Agent",
			searchPattern:  "ucxl://alice:*@*:*/*",
			expectedCount:  2,
			expectedAgents: []string{"alice", "alice"},
		},
		{
			name:           "Search_Admin_ProjectB",
			searchPattern:  "ucxl://*:admin@projectB:*/*",
			expectedCount:  1,
			expectedAgents: []string{"charlie"},
		},
		{
			name:           "Search_Feature_Tasks",
			searchPattern:  "ucxl://*:*@*:feature*/*",
			expectedCount:  2,
			expectedAgents: []string{"alice", "alice"},
		},
	}

	for _, tc := range searchTestCases {
		t.Run(tc.name, func(t *testing.T) {
			// Perform search using UCXI discover endpoint
			searchResp, err := http.Get(fmt.Sprintf("%s/discover?pattern=%s", suite.httpServer.URL, tc.searchPattern))
			require.NoError(t, err, "Search request failed")
			require.Equal(t, http.StatusOK, searchResp.StatusCode, "Search should succeed")

			var searchResults map[string]interface{}
			err = json.NewDecoder(searchResp.Body).Decode(&searchResults)
			require.NoError(t, err, "Failed to decode search results")
			searchResp.Body.Close()

			// Verify search results
			results, ok := searchResults["results"].([]interface{})
			require.True(t, ok, "Search results should contain results array")
			assert.Len(t, results, tc.expectedCount, "Should find expected number of results")

			// Verify that expected agents are found
			foundAgents := make(map[string]int)
			for _, result := range results {
				resultMap := result.(map[string]interface{})
				address := resultMap["address"].(string)
				parsed, err := ucxl.ParseUCXLAddress(address)
				require.NoError(t, err, "Should be able to parse result address")
				foundAgents[parsed.Agent]++
			}

			for _, expectedAgent := range tc.expectedAgents {
				assert.Greater(t, foundAgents[expectedAgent], 0, "Should find expected agent: %s", expectedAgent)
			}
		})
	}
}

// TestTemporalAddressing tests temporal navigation functionality
func (suite *UCXIDHTIntegrationTestSuite) TestTemporalAddressing(t *testing.T) {
	baseAddress := "ucxl://agent1:developer@project1:task1/*"
	
	// Create multiple versions
	versions := []struct {
		address string
		data    []byte
		version string
	}{
		{baseAddress + "v1", []byte(`{"version": 1, "data": "first version"}`), "v1"},
		{baseAddress + "v2", []byte(`{"version": 2, "data": "second version"}`), "v2"},
		{baseAddress + "v3", []byte(`{"version": 3, "data": "third version"}`), "v3"},
		{baseAddress + "^", []byte(`{"version": 999, "data": "latest version"}`), "latest"},
	}

	// Store all versions
	for _, v := range versions {
		putResp, err := http.Post(
			fmt.Sprintf("%s/put/%s", suite.httpServer.URL, v.address),
			"application/json",
			bytes.NewReader(v.data),
		)
		require.NoError(t, err, "Failed to store version")
		require.Equal(t, http.StatusOK, putResp.StatusCode, "PUT should succeed")
		putResp.Body.Close()
	}

	// Test temporal navigation
	navigationTests := []struct {
		name       string
		address    string
		expectData string
	}{
		{
			name:       "Latest_Version",
			address:    baseAddress + "^",
			expectData: "latest version",
		},
		{
			name:       "Specific_Version_v2",
			address:    baseAddress + "v2",
			expectData: "second version",
		},
		{
			name:       "Backward_Navigation",
			address:    baseAddress + "^-1", // Latest minus 1
			expectData: "third version",
		},
	}

	for _, nt := range navigationTests {
		t.Run(nt.name, func(t *testing.T) {
			getResp, err := http.Get(fmt.Sprintf("%s/get/%s", suite.httpServer.URL, nt.address))
			require.NoError(t, err, "GET request failed")
			require.Equal(t, http.StatusOK, getResp.StatusCode, "GET should succeed")

			var result map[string]interface{}
			err = json.NewDecoder(getResp.Body).Decode(&result)
			require.NoError(t, err, "Failed to decode response")
			getResp.Body.Close()

			assert.Contains(t, result["data"], nt.expectData, "Should retrieve correct version")
		})
	}
}

// TestInvalidAddressValidation tests that invalid addresses return proper UCXL-400 codes
func (suite *UCXIDHTIntegrationTestSuite) TestInvalidAddressValidation(t *testing.T) {
	invalidAddresses := []struct {
		address string
		reason  string
	}{
		{"invalid-address", "missing scheme"},
		{"ucxl://", "empty address components"},
		{"ucxl://:role@project:task/*", "empty agent"},
		{"ucxl://agent:@project:task/*", "empty role"},
		{"ucxl://agent:role@:task/*", "empty project"},
		{"ucxl://agent:role@project:/*", "empty task"},
		{"http://agent:role@project:task/*", "wrong scheme"},
		{"ucxl://ag@ent:role@project:task/*", "invalid characters"},
		{"ucxl://agent:role@project:task", "missing temporal segment"},
	}

	testData := []byte(`{"test": "data"}`)

	for _, ia := range invalidAddresses {
		t.Run(fmt.Sprintf("Invalid_%s", strings.ReplaceAll(ia.reason, " ", "_")), func(t *testing.T) {
			// Test PUT with invalid address
			putResp, err := http.Post(
				fmt.Sprintf("%s/put/%s", suite.httpServer.URL, ia.address),
				"application/json",
				bytes.NewReader(testData),
			)
			require.NoError(t, err, "PUT request should complete")
			assert.Equal(t, http.StatusBadRequest, putResp.StatusCode, 
				"PUT with invalid address should return 400: %s", ia.reason)
			putResp.Body.Close()

			// Test GET with invalid address
			getResp, err := http.Get(fmt.Sprintf("%s/get/%s", suite.httpServer.URL, ia.address))
			require.NoError(t, err, "GET request should complete")
			assert.Equal(t, http.StatusBadRequest, getResp.StatusCode, 
				"GET with invalid address should return 400: %s", ia.reason)
			getResp.Body.Close()
		})
	}
}

// TestConcurrentOperations tests thread safety under concurrent access
func (suite *UCXIDHTIntegrationTestSuite) TestConcurrentOperations(t *testing.T) {
	const numGoroutines = 10
	const operationsPerGoroutine = 50

	errChan := make(chan error, numGoroutines*operationsPerGoroutine)
	doneChan := make(chan bool, numGoroutines)

	// Start concurrent operations
	for i := 0; i < numGoroutines; i++ {
		go func(goroutineID int) {
			defer func() { doneChan <- true }()

			for j := 0; j < operationsPerGoroutine; j++ {
				address := fmt.Sprintf("ucxl://worker%d:developer@project:task%d/*^", goroutineID, j)
				testData := []byte(fmt.Sprintf(`{"worker": %d, "operation": %d}`, goroutineID, j))

				// PUT operation
				putResp, err := http.Post(
					fmt.Sprintf("%s/put/%s", suite.httpServer.URL, address),
					"application/json",
					bytes.NewReader(testData),
				)
				if err != nil {
					errChan <- fmt.Errorf("PUT failed for worker %d operation %d: %v", goroutineID, j, err)
					continue
				}
				putResp.Body.Close()

				if putResp.StatusCode != http.StatusOK {
					errChan <- fmt.Errorf("PUT returned %d for worker %d operation %d", 
						putResp.StatusCode, goroutineID, j)
					continue
				}

				// GET operation
				getResp, err := http.Get(fmt.Sprintf("%s/get/%s", suite.httpServer.URL, address))
				if err != nil {
					errChan <- fmt.Errorf("GET failed for worker %d operation %d: %v", goroutineID, j, err)
					continue
				}
				getResp.Body.Close()

				if getResp.StatusCode != http.StatusOK {
					errChan <- fmt.Errorf("GET returned %d for worker %d operation %d", 
						getResp.StatusCode, goroutineID, j)
					continue
				}
			}
		}(i)
	}

	// Wait for all goroutines to complete
	for i := 0; i < numGoroutines; i++ {
		<-doneChan
	}
	close(errChan)

	// Check for errors
	var errors []error
	for err := range errChan {
		errors = append(errors, err)
	}

	if len(errors) > 0 {
		t.Errorf("Concurrent operations failed with %d errors:", len(errors))
		for _, err := range errors[:min(10, len(errors))] { // Show first 10 errors
			t.Errorf("  - %v", err)
		}
	}

	// Verify final storage state
	stats := suite.dhtStorage.GetStats()
	expectedKeys := numGoroutines * operationsPerGoroutine
	assert.Equal(t, expectedKeys, stats.TotalKeys, "Should have stored all keys successfully")
}

// TestLargePayloadHandling tests storage and retrieval of large payloads
func (suite *UCXIDHTIntegrationTestSuite) TestLargePayloadHandling(t *testing.T) {
	payloadSizes := []struct {
		name string
		size int
	}{
		{"1KB", 1024},
		{"10KB", 10 * 1024},
		{"100KB", 100 * 1024},
		{"1MB", 1024 * 1024},
	}

	for _, ps := range payloadSizes {
		t.Run(fmt.Sprintf("Payload_%s", ps.name), func(t *testing.T) {
			// Generate large payload
			payload := make([]byte, ps.size)
			for i := range payload {
				payload[i] = byte(i % 256)
			}

			address := fmt.Sprintf("ucxl://tester:developer@large:payload_%s/*^", ps.name)

			start := time.Now()

			// Store large payload
			putResp, err := http.Post(
				fmt.Sprintf("%s/put/%s", suite.httpServer.URL, address),
				"application/octet-stream",
				bytes.NewReader(payload),
			)
			require.NoError(t, err, "PUT request failed")
			require.Equal(t, http.StatusOK, putResp.StatusCode, "PUT should succeed")
			putResp.Body.Close()

			putTime := time.Since(start)

			// Retrieve large payload
			start = time.Now()
			getResp, err := http.Get(fmt.Sprintf("%s/get/%s", suite.httpServer.URL, address))
			require.NoError(t, err, "GET request failed")
			require.Equal(t, http.StatusOK, getResp.StatusCode, "GET should succeed")

			var retrieved bytes.Buffer
			_, err = retrieved.ReadFrom(getResp.Body)
			require.NoError(t, err, "Failed to read response")
			getResp.Body.Close()

			getTime := time.Since(start)

			// Verify payload integrity
			assert.Equal(t, payload, retrieved.Bytes(), "Retrieved payload should match original")

			t.Logf("Payload %s: PUT=%v, GET=%v, Size=%d bytes", 
				ps.name, putTime, getTime, len(payload))

			// Performance assertions (reasonable thresholds for test environment)
			assert.Less(t, putTime, time.Second*10, "PUT should complete within 10 seconds")
			assert.Less(t, getTime, time.Second*10, "GET should complete within 10 seconds")
		})
	}
}

// TestTTLExpirationCleanup tests TTL-based expiration and cleanup
func (suite *UCXIDHTIntegrationTestSuite) TestTTLExpirationCleanup(t *testing.T) {
	// This test requires a mock DHT that supports TTL
	// For now, we'll test the API behavior and assume the underlying storage respects TTL
	
	shortTTLAddress := "ucxl://temp:developer@project:shortlived/*^"
	testData := []byte(`{"ttl": "short", "data": "should expire soon"}`)

	// Store data with short TTL (this would need to be configured in the storage layer)
	putResp, err := http.Post(
		fmt.Sprintf("%s/put/%s", suite.httpServer.URL, shortTTLAddress),
		"application/json",
		bytes.NewReader(testData),
	)
	require.NoError(t, err, "PUT request failed")
	require.Equal(t, http.StatusOK, putResp.StatusCode, "PUT should succeed")
	putResp.Body.Close()

	// Immediate retrieval should work
	getResp, err := http.Get(fmt.Sprintf("%s/get/%s", suite.httpServer.URL, shortTTLAddress))
	require.NoError(t, err, "GET request failed")
	require.Equal(t, http.StatusOK, getResp.StatusCode, "GET should succeed immediately")
	getResp.Body.Close()

	// Test health endpoint to ensure server is responsive
	healthResp, err := http.Get(fmt.Sprintf("%s/health", suite.httpServer.URL))
	require.NoError(t, err, "Health check failed")
	require.Equal(t, http.StatusOK, healthResp.StatusCode, "Health check should pass")
	
	var healthData map[string]interface{}
	err = json.NewDecoder(healthResp.Body).Decode(&healthData)
	require.NoError(t, err, "Failed to decode health response")
	healthResp.Body.Close()

	assert.Equal(t, "healthy", healthData["status"], "Server should be healthy")
	
	t.Logf("TTL expiration test completed - would need real TTL implementation for full testing")
}

func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}