package handlers

import (
	"bytes"
	"encoding/json"
	"errors"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/Wikid82/charon/backend/internal/models"
	"github.com/Wikid82/charon/backend/internal/services"
	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	"gorm.io/driver/sqlite"
	"gorm.io/gorm"
)

// mockSecurityNotificationService implements the service interface for controlled testing.
type mockSecurityNotificationService struct {
	getSettingsFunc    func() (*models.NotificationConfig, error)
	updateSettingsFunc func(*models.NotificationConfig) error
}

func (m *mockSecurityNotificationService) GetSettings() (*models.NotificationConfig, error) {
	if m.getSettingsFunc != nil {
		return m.getSettingsFunc()
	}
	return &models.NotificationConfig{}, nil
}

func (m *mockSecurityNotificationService) UpdateSettings(c *models.NotificationConfig) error {
	if m.updateSettingsFunc != nil {
		return m.updateSettingsFunc(c)
	}
	return nil
}

func setupSecNotifTestDB(t *testing.T) *gorm.DB {
	db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{})
	require.NoError(t, err)
	require.NoError(t, db.AutoMigrate(&models.NotificationConfig{}))
	return db
}

// TestNewSecurityNotificationHandler verifies constructor returns non-nil handler.
func TestNewSecurityNotificationHandler(t *testing.T) {
	t.Parallel()

	db := setupSecNotifTestDB(t)
	svc := services.NewSecurityNotificationService(db)
	handler := NewSecurityNotificationHandler(svc)

	assert.NotNil(t, handler, "Handler should not be nil")
}

// TestSecurityNotificationHandler_GetSettings_Success tests successful settings retrieval.
func TestSecurityNotificationHandler_GetSettings_Success(t *testing.T) {
	t.Parallel()

	expectedConfig := &models.NotificationConfig{
		ID:              "test-id",
		Enabled:         true,
		MinLogLevel:     "warn",
		WebhookURL:      "https://example.com/webhook",
		NotifyWAFBlocks: true,
		NotifyACLDenies: false,
	}

	mockService := &mockSecurityNotificationService{
		getSettingsFunc: func() (*models.NotificationConfig, error) {
			return expectedConfig, nil
		},
	}

	handler := NewSecurityNotificationHandler(mockService)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/api/v1/security/notifications/settings", http.NoBody)

	handler.GetSettings(c)

	assert.Equal(t, http.StatusOK, w.Code)

	var config models.NotificationConfig
	err := json.Unmarshal(w.Body.Bytes(), &config)
	require.NoError(t, err)

	assert.Equal(t, expectedConfig.ID, config.ID)
	assert.Equal(t, expectedConfig.Enabled, config.Enabled)
	assert.Equal(t, expectedConfig.MinLogLevel, config.MinLogLevel)
	assert.Equal(t, expectedConfig.WebhookURL, config.WebhookURL)
	assert.Equal(t, expectedConfig.NotifyWAFBlocks, config.NotifyWAFBlocks)
	assert.Equal(t, expectedConfig.NotifyACLDenies, config.NotifyACLDenies)
}

// TestSecurityNotificationHandler_GetSettings_ServiceError tests service error handling.
func TestSecurityNotificationHandler_GetSettings_ServiceError(t *testing.T) {
	t.Parallel()

	mockService := &mockSecurityNotificationService{
		getSettingsFunc: func() (*models.NotificationConfig, error) {
			return nil, errors.New("database connection failed")
		},
	}

	handler := NewSecurityNotificationHandler(mockService)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/api/v1/security/notifications/settings", http.NoBody)

	handler.GetSettings(c)

	assert.Equal(t, http.StatusInternalServerError, w.Code)

	var response map[string]string
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	assert.Contains(t, response["error"], "Failed to retrieve settings")
}

// TestSecurityNotificationHandler_UpdateSettings_InvalidJSON tests malformed JSON handling.
func TestSecurityNotificationHandler_UpdateSettings_InvalidJSON(t *testing.T) {
	t.Parallel()

	mockService := &mockSecurityNotificationService{}
	handler := NewSecurityNotificationHandler(mockService)

	malformedJSON := []byte(`{enabled: true, "min_log_level": "error"`)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	setAdminContext(c)
	c.Request = httptest.NewRequest("PUT", "/settings", bytes.NewBuffer(malformedJSON))
	c.Request.Header.Set("Content-Type", "application/json")

	handler.UpdateSettings(c)

	assert.Equal(t, http.StatusBadRequest, w.Code)

	var response map[string]string
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	assert.Contains(t, response["error"], "Invalid request body")
}

// TestSecurityNotificationHandler_UpdateSettings_InvalidMinLogLevel tests invalid log level rejection.
func TestSecurityNotificationHandler_UpdateSettings_InvalidMinLogLevel(t *testing.T) {
	t.Parallel()

	invalidLevels := []struct {
		name  string
		level string
	}{
		{"trace", "trace"},
		{"critical", "critical"},
		{"fatal", "fatal"},
		{"unknown", "unknown"},
	}

	for _, tc := range invalidLevels {
		t.Run(tc.name, func(t *testing.T) {
			mockService := &mockSecurityNotificationService{}
			handler := NewSecurityNotificationHandler(mockService)

			config := models.NotificationConfig{
				Enabled:         true,
				MinLogLevel:     tc.level,
				NotifyWAFBlocks: true,
			}

			body, err := json.Marshal(config)
			require.NoError(t, err)

			gin.SetMode(gin.TestMode)
			w := httptest.NewRecorder()
			c, _ := gin.CreateTestContext(w)
			setAdminContext(c)
			c.Request = httptest.NewRequest("PUT", "/settings", bytes.NewBuffer(body))
			c.Request.Header.Set("Content-Type", "application/json")

			handler.UpdateSettings(c)

			assert.Equal(t, http.StatusBadRequest, w.Code)

			var response map[string]string
			err = json.Unmarshal(w.Body.Bytes(), &response)
			require.NoError(t, err)

			assert.Contains(t, response["error"], "Invalid min_log_level")
		})
	}
}

// TestSecurityNotificationHandler_UpdateSettings_InvalidWebhookURL_SSRF tests SSRF protection.
func TestSecurityNotificationHandler_UpdateSettings_InvalidWebhookURL_SSRF(t *testing.T) {
	t.Parallel()

	ssrfURLs := []struct {
		name string
		url  string
	}{
		{"AWS Metadata", "http://169.254.169.254/latest/meta-data/"},
		{"GCP Metadata", "http://metadata.google.internal/computeMetadata/v1/"},
		{"Azure Metadata", "http://169.254.169.254/metadata/instance"},
		{"Private IP 10.x", "http://10.0.0.1/admin"},
		{"Private IP 172.16.x", "http://172.16.0.1/config"},
		{"Private IP 192.168.x", "http://192.168.1.1/api"},
		{"Link-local", "http://169.254.1.1/"},
	}

	for _, tc := range ssrfURLs {
		t.Run(tc.name, func(t *testing.T) {
			mockService := &mockSecurityNotificationService{}
			handler := NewSecurityNotificationHandler(mockService)

			config := models.NotificationConfig{
				Enabled:         true,
				MinLogLevel:     "error",
				WebhookURL:      tc.url,
				NotifyWAFBlocks: true,
			}

			body, err := json.Marshal(config)
			require.NoError(t, err)

			gin.SetMode(gin.TestMode)
			w := httptest.NewRecorder()
			c, _ := gin.CreateTestContext(w)
			setAdminContext(c)
			c.Request = httptest.NewRequest("PUT", "/settings", bytes.NewBuffer(body))
			c.Request.Header.Set("Content-Type", "application/json")

			handler.UpdateSettings(c)

			assert.Equal(t, http.StatusBadRequest, w.Code)

			var response map[string]interface{}
			err = json.Unmarshal(w.Body.Bytes(), &response)
			require.NoError(t, err)

			assert.Contains(t, response["error"], "Invalid webhook URL")
			if help, ok := response["help"]; ok {
				assert.Contains(t, help, "private networks")
			}
		})
	}
}

// TestSecurityNotificationHandler_UpdateSettings_PrivateIPWebhook tests private IP handling.
func TestSecurityNotificationHandler_UpdateSettings_PrivateIPWebhook(t *testing.T) {
	t.Parallel()

	// Note: localhost is allowed by WithAllowLocalhost() option
	localhostURLs := []string{
		"http://127.0.0.1/hook",
		"http://localhost/webhook",
		"http://[::1]/api",
	}

	for _, url := range localhostURLs {
		t.Run(url, func(t *testing.T) {
			mockService := &mockSecurityNotificationService{
				updateSettingsFunc: func(c *models.NotificationConfig) error {
					return nil
				},
			}
			handler := NewSecurityNotificationHandler(mockService)

			config := models.NotificationConfig{
				Enabled:     true,
				MinLogLevel: "warn",
				WebhookURL:  url,
			}

			body, err := json.Marshal(config)
			require.NoError(t, err)

			gin.SetMode(gin.TestMode)
			w := httptest.NewRecorder()
			c, _ := gin.CreateTestContext(w)
			setAdminContext(c)
			c.Request = httptest.NewRequest("PUT", "/settings", bytes.NewBuffer(body))
			c.Request.Header.Set("Content-Type", "application/json")

			handler.UpdateSettings(c)

			// Localhost should be allowed with AllowLocalhost option
			assert.Equal(t, http.StatusOK, w.Code, "Localhost should be allowed: %s", url)
		})
	}
}

// TestSecurityNotificationHandler_UpdateSettings_ServiceError tests database error handling.
func TestSecurityNotificationHandler_UpdateSettings_ServiceError(t *testing.T) {
	t.Parallel()

	mockService := &mockSecurityNotificationService{
		updateSettingsFunc: func(c *models.NotificationConfig) error {
			return errors.New("database write failed")
		},
	}

	handler := NewSecurityNotificationHandler(mockService)

	config := models.NotificationConfig{
		Enabled:         true,
		MinLogLevel:     "error",
		WebhookURL:      "http://localhost:9090/webhook", // Use localhost
		NotifyWAFBlocks: true,
	}

	body, err := json.Marshal(config)
	require.NoError(t, err)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	setAdminContext(c)
	c.Request = httptest.NewRequest("PUT", "/settings", bytes.NewBuffer(body))
	c.Request.Header.Set("Content-Type", "application/json")

	handler.UpdateSettings(c)

	assert.Equal(t, http.StatusInternalServerError, w.Code)

	var response map[string]string
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	assert.Contains(t, response["error"], "Failed to update settings")
}

// TestSecurityNotificationHandler_UpdateSettings_Success tests successful settings update.
func TestSecurityNotificationHandler_UpdateSettings_Success(t *testing.T) {
	t.Parallel()

	var capturedConfig *models.NotificationConfig

	mockService := &mockSecurityNotificationService{
		updateSettingsFunc: func(c *models.NotificationConfig) error {
			capturedConfig = c
			return nil
		},
	}

	handler := NewSecurityNotificationHandler(mockService)

	config := models.NotificationConfig{
		Enabled:         true,
		MinLogLevel:     "warn",
		WebhookURL:      "http://localhost:8080/security", // Use localhost which is allowed
		NotifyWAFBlocks: true,
		NotifyACLDenies: false,
	}

	body, err := json.Marshal(config)
	require.NoError(t, err)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	setAdminContext(c)
	c.Request = httptest.NewRequest("PUT", "/settings", bytes.NewBuffer(body))
	c.Request.Header.Set("Content-Type", "application/json")

	handler.UpdateSettings(c)

	assert.Equal(t, http.StatusOK, w.Code)

	var response map[string]string
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	assert.Equal(t, "Settings updated successfully", response["message"])

	// Verify the service was called with the correct config
	require.NotNil(t, capturedConfig)
	assert.Equal(t, config.Enabled, capturedConfig.Enabled)
	assert.Equal(t, config.MinLogLevel, capturedConfig.MinLogLevel)
	assert.Equal(t, config.WebhookURL, capturedConfig.WebhookURL)
	assert.Equal(t, config.NotifyWAFBlocks, capturedConfig.NotifyWAFBlocks)
	assert.Equal(t, config.NotifyACLDenies, capturedConfig.NotifyACLDenies)
}

// TestSecurityNotificationHandler_UpdateSettings_EmptyWebhookURL tests empty webhook is valid.
func TestSecurityNotificationHandler_UpdateSettings_EmptyWebhookURL(t *testing.T) {
	t.Parallel()

	mockService := &mockSecurityNotificationService{
		updateSettingsFunc: func(c *models.NotificationConfig) error {
			return nil
		},
	}

	handler := NewSecurityNotificationHandler(mockService)

	config := models.NotificationConfig{
		Enabled:         true,
		MinLogLevel:     "info",
		WebhookURL:      "",
		NotifyWAFBlocks: true,
		NotifyACLDenies: true,
	}

	body, err := json.Marshal(config)
	require.NoError(t, err)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	setAdminContext(c)
	c.Request = httptest.NewRequest("PUT", "/settings", bytes.NewBuffer(body))
	c.Request.Header.Set("Content-Type", "application/json")

	handler.UpdateSettings(c)

	assert.Equal(t, http.StatusOK, w.Code)

	var response map[string]string
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	assert.Equal(t, "Settings updated successfully", response["message"])
}

func TestSecurityNotificationHandler_RouteAliasGet(t *testing.T) {
	t.Parallel()

	expectedConfig := &models.NotificationConfig{
		ID:              "alias-test-id",
		Enabled:         true,
		MinLogLevel:     "info",
		WebhookURL:      "https://example.com/webhook",
		NotifyWAFBlocks: true,
		NotifyACLDenies: true,
	}

	mockService := &mockSecurityNotificationService{
		getSettingsFunc: func() (*models.NotificationConfig, error) {
			return expectedConfig, nil
		},
	}

	handler := NewSecurityNotificationHandler(mockService)

	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.GET("/api/v1/security/notifications/settings", handler.GetSettings)
	router.GET("/api/v1/notifications/settings/security", handler.GetSettings)

	originalWriter := httptest.NewRecorder()
	originalRequest := httptest.NewRequest(http.MethodGet, "/api/v1/security/notifications/settings", http.NoBody)
	router.ServeHTTP(originalWriter, originalRequest)

	aliasWriter := httptest.NewRecorder()
	aliasRequest := httptest.NewRequest(http.MethodGet, "/api/v1/notifications/settings/security", http.NoBody)
	router.ServeHTTP(aliasWriter, aliasRequest)

	assert.Equal(t, http.StatusOK, originalWriter.Code)
	assert.Equal(t, originalWriter.Code, aliasWriter.Code)
	assert.Equal(t, originalWriter.Body.String(), aliasWriter.Body.String())
}

func TestSecurityNotificationHandler_RouteAliasUpdate(t *testing.T) {
	t.Parallel()

	legacyUpdates := 0
	canonicalUpdates := 0
	mockService := &mockSecurityNotificationService{
		updateSettingsFunc: func(c *models.NotificationConfig) error {
			if c.WebhookURL == "http://localhost:8080/security" {
				canonicalUpdates++
			}
			return nil
		},
	}

	handler := NewSecurityNotificationHandler(mockService)

	config := models.NotificationConfig{
		Enabled:         true,
		MinLogLevel:     "warn",
		WebhookURL:      "http://localhost:8080/security",
		NotifyWAFBlocks: true,
		NotifyACLDenies: false,
	}

	body, err := json.Marshal(config)
	require.NoError(t, err)

	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(func(c *gin.Context) {
		setAdminContext(c)
		c.Next()
	})
	router.PUT("/api/v1/security/notifications/settings", handler.DeprecatedUpdateSettings)
	router.PUT("/api/v1/notifications/settings/security", handler.UpdateSettings)

	originalWriter := httptest.NewRecorder()
	originalRequest := httptest.NewRequest(http.MethodPut, "/api/v1/security/notifications/settings", bytes.NewBuffer(body))
	originalRequest.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(originalWriter, originalRequest)

	aliasWriter := httptest.NewRecorder()
	aliasRequest := httptest.NewRequest(http.MethodPut, "/api/v1/notifications/settings/security", bytes.NewBuffer(body))
	aliasRequest.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(aliasWriter, aliasRequest)

	assert.Equal(t, http.StatusGone, originalWriter.Code)
	assert.Equal(t, "true", originalWriter.Header().Get("X-Charon-Deprecated"))
	assert.Equal(t, "/api/v1/notifications/settings/security", originalWriter.Header().Get("X-Charon-Canonical-Endpoint"))

	assert.Equal(t, http.StatusOK, aliasWriter.Code)
	assert.Equal(t, 0, legacyUpdates)
	assert.Equal(t, 1, canonicalUpdates)
}

func TestSecurityNotificationHandler_DeprecatedRouteHeaders(t *testing.T) {
	t.Parallel()

	mockService := &mockSecurityNotificationService{
		getSettingsFunc: func() (*models.NotificationConfig, error) {
			return &models.NotificationConfig{Enabled: true, MinLogLevel: "warn"}, nil
		},
		updateSettingsFunc: func(c *models.NotificationConfig) error {
			return nil
		},
	}

	handler := NewSecurityNotificationHandler(mockService)

	gin.SetMode(gin.TestMode)
	router := gin.New()
	router.Use(func(c *gin.Context) {
		setAdminContext(c)
		c.Next()
	})
	router.GET("/api/v1/security/notifications/settings", handler.DeprecatedGetSettings)
	router.PUT("/api/v1/security/notifications/settings", handler.DeprecatedUpdateSettings)
	router.GET("/api/v1/notifications/settings/security", handler.GetSettings)
	router.PUT("/api/v1/notifications/settings/security", handler.UpdateSettings)

	legacyGet := httptest.NewRecorder()
	legacyGetReq := httptest.NewRequest(http.MethodGet, "/api/v1/security/notifications/settings", http.NoBody)
	router.ServeHTTP(legacyGet, legacyGetReq)
	require.Equal(t, http.StatusOK, legacyGet.Code)
	assert.Equal(t, "true", legacyGet.Header().Get("X-Charon-Deprecated"))
	assert.Equal(t, "/api/v1/notifications/settings/security", legacyGet.Header().Get("X-Charon-Canonical-Endpoint"))

	canonicalGet := httptest.NewRecorder()
	canonicalGetReq := httptest.NewRequest(http.MethodGet, "/api/v1/notifications/settings/security", http.NoBody)
	router.ServeHTTP(canonicalGet, canonicalGetReq)
	require.Equal(t, http.StatusOK, canonicalGet.Code)
	assert.Empty(t, canonicalGet.Header().Get("X-Charon-Deprecated"))

	body, err := json.Marshal(models.NotificationConfig{Enabled: true, MinLogLevel: "warn"})
	require.NoError(t, err)

	legacyPut := httptest.NewRecorder()
	legacyPutReq := httptest.NewRequest(http.MethodPut, "/api/v1/security/notifications/settings", bytes.NewBuffer(body))
	legacyPutReq.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(legacyPut, legacyPutReq)
	require.Equal(t, http.StatusGone, legacyPut.Code)
	assert.Equal(t, "true", legacyPut.Header().Get("X-Charon-Deprecated"))
	assert.Equal(t, "/api/v1/notifications/settings/security", legacyPut.Header().Get("X-Charon-Canonical-Endpoint"))

	var legacyBody map[string]string
	err = json.Unmarshal(legacyPut.Body.Bytes(), &legacyBody)
	require.NoError(t, err)
	assert.Len(t, legacyBody, 2)
	assert.Equal(t, "This endpoint is deprecated and no longer accepts updates", legacyBody["error"])
	assert.Equal(t, "/api/v1/notifications/settings/security", legacyBody["canonical_endpoint"])

	canonicalPut := httptest.NewRecorder()
	canonicalPutReq := httptest.NewRequest(http.MethodPut, "/api/v1/notifications/settings/security", bytes.NewBuffer(body))
	canonicalPutReq.Header.Set("Content-Type", "application/json")
	router.ServeHTTP(canonicalPut, canonicalPutReq)
	require.Equal(t, http.StatusOK, canonicalPut.Code)
}

func TestNormalizeEmailRecipients(t *testing.T) {
	tests := []struct {
		name    string
		input   string
		want    string
		wantErr string
	}{
		{
			name:  "empty input",
			input: "   ",
			want:  "",
		},
		{
			name:  "single valid",
			input: "admin@example.com",
			want:  "admin@example.com",
		},
		{
			name:  "multiple valid with spaces and blanks",
			input: " admin@example.com, , ops@example.com ,security@example.com ",
			want:  "admin@example.com, ops@example.com, security@example.com",
		},
		{
			name:  "duplicates and mixed case preserved",
			input: "Admin@Example.com, admin@example.com, Admin@Example.com",
			want:  "Admin@Example.com, admin@example.com, Admin@Example.com",
		},
		{
			name:    "invalid only",
			input:   "not-an-email",
			wantErr: "invalid email recipients: not-an-email",
		},
		{
			name:    "mixed invalid and valid",
			input:   "admin@example.com, bad-address,ops@example.com",
			wantErr: "invalid email recipients: bad-address",
		},
		{
			name:    "multiple invalids",
			input:   "bad-address,also-bad",
			wantErr: "invalid email recipients: bad-address, also-bad",
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			got, err := normalizeEmailRecipients(tt.input)
			if tt.wantErr != "" {
				require.Error(t, err)
				assert.Equal(t, tt.wantErr, err.Error())
				return
			}

			require.NoError(t, err)
			assert.Equal(t, tt.want, got)
		})
	}
}

// TestSecurityNotificationHandler_DeprecatedUpdateSettings_AllFields tests that all JSON fields are returned
func TestSecurityNotificationHandler_DeprecatedUpdateSettings_AllFields(t *testing.T) {
	t.Parallel()

	mockService := &mockSecurityNotificationService{}
	handler := NewSecurityNotificationHandler(mockService)

	body, err := json.Marshal(models.NotificationConfig{Enabled: true, MinLogLevel: "warn"})
	require.NoError(t, err)

	gin.SetMode(gin.TestMode)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	setAdminContext(c)
	c.Request = httptest.NewRequest(http.MethodPut, "/api/v1/security/notifications/settings", bytes.NewBuffer(body))
	c.Request.Header.Set("Content-Type", "application/json")

	handler.DeprecatedUpdateSettings(c)

	assert.Equal(t, http.StatusGone, w.Code)
	assert.Equal(t, "true", w.Header().Get("X-Charon-Deprecated"))
	assert.Equal(t, "/api/v1/notifications/settings/security", w.Header().Get("X-Charon-Canonical-Endpoint"))

	var response map[string]string
	err = json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	// Verify both JSON fields are present with exact values
	assert.Equal(t, "This endpoint is deprecated and no longer accepts updates", response["error"])
	assert.Equal(t, "/api/v1/notifications/settings/security", response["canonical_endpoint"])
	assert.Len(t, response, 2, "Should have exactly 2 fields in JSON response")
}
