package rest_test

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

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/interfaces/rest"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// ---------------------------------------------------------------------------
// StreamingHandler.Handshake — creates new session
// ---------------------------------------------------------------------------

func TestStreamingHandler_Handshake(t *testing.T) {
	handler := rest.NewStreamingHandler(nil)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/cometd/1.0/handshake", nil)

	handler.Handshake(c)

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

	var resp []map[string]interface{}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
	require.Len(t, resp, 1)

	assert.Equal(t, "/meta/handshake", resp[0]["channel"])
	assert.Equal(t, true, resp[0]["successful"])
	assert.NotEmpty(t, resp[0]["clientId"])
}

// ---------------------------------------------------------------------------
// StreamingHandler.Disconnect — destroys session
// ---------------------------------------------------------------------------

func TestStreamingHandler_Disconnect(t *testing.T) {
	handler := rest.NewStreamingHandler(nil)

	// First handshake to get a clientId
	w1 := httptest.NewRecorder()
	c1, _ := gin.CreateTestContext(w1)
	c1.Request = httptest.NewRequest("POST", "/cometd/1.0/handshake", nil)
	handler.Handshake(c1)

	var handshakeResp []map[string]interface{}
	require.NoError(t, json.Unmarshal(w1.Body.Bytes(), &handshakeResp))
	clientID := handshakeResp[0]["clientId"].(string)

	// Now disconnect
	disconnectBody := []map[string]interface{}{
		{"clientId": clientID, "channel": "/meta/disconnect"},
	}
	bodyBytes, _ := json.Marshal(disconnectBody)

	w2 := httptest.NewRecorder()
	c2, _ := gin.CreateTestContext(w2)
	c2.Request = httptest.NewRequest("POST", "/cometd/1.0/disconnect", bytes.NewReader(bodyBytes))
	c2.Request.Header.Set("Content-Type", "application/json")

	handler.Disconnect(c2)

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

	var resp []map[string]interface{}
	require.NoError(t, json.Unmarshal(w2.Body.Bytes(), &resp))
	require.Len(t, resp, 1)
	assert.Equal(t, "/meta/disconnect", resp[0]["channel"])
	assert.Equal(t, true, resp[0]["successful"])
	assert.Equal(t, clientID, resp[0]["clientId"])
}

// ---------------------------------------------------------------------------
// StreamingHandler.Subscribe — validates session
// ---------------------------------------------------------------------------

func TestStreamingHandler_Subscribe_ValidSession(t *testing.T) {
	handler := rest.NewStreamingHandler(nil)

	// Handshake first
	w1 := httptest.NewRecorder()
	c1, _ := gin.CreateTestContext(w1)
	c1.Request = httptest.NewRequest("POST", "/cometd/1.0/handshake", nil)
	handler.Handshake(c1)

	var handshakeResp []map[string]interface{}
	require.NoError(t, json.Unmarshal(w1.Body.Bytes(), &handshakeResp))
	clientID := handshakeResp[0]["clientId"].(string)

	// Subscribe
	subscribeBody := []map[string]interface{}{
		{
			"clientId":     clientID,
			"subscription": "/event/AccountChange__e",
		},
	}
	bodyBytes, _ := json.Marshal(subscribeBody)

	w2 := httptest.NewRecorder()
	c2, _ := gin.CreateTestContext(w2)
	c2.Request = httptest.NewRequest("POST", "/cometd/1.0/subscribe", bytes.NewReader(bodyBytes))
	c2.Request.Header.Set("Content-Type", "application/json")

	handler.Subscribe(c2)

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

	var resp []map[string]interface{}
	require.NoError(t, json.Unmarshal(w2.Body.Bytes(), &resp))
	require.Len(t, resp, 1)
	assert.Equal(t, true, resp[0]["successful"])
	assert.Equal(t, "/event/AccountChange__e", resp[0]["subscription"])
}

func TestStreamingHandler_Subscribe_InvalidSession(t *testing.T) {
	handler := rest.NewStreamingHandler(nil)

	// Subscribe with unknown clientId (no handshake)
	subscribeBody := []map[string]interface{}{
		{
			"clientId":     "fake-client-id",
			"subscription": "/event/AccountChange__e",
		},
	}
	bodyBytes, _ := json.Marshal(subscribeBody)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/cometd/1.0/subscribe", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")

	handler.Subscribe(c)

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

	var resp []map[string]interface{}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
	require.Len(t, resp, 1)
	assert.Equal(t, false, resp[0]["successful"])
}

func TestStreamingHandler_Subscribe_InvalidJSON(t *testing.T) {
	handler := rest.NewStreamingHandler(nil)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/cometd/1.0/subscribe", bytes.NewReader([]byte(`{bad}`)))
	c.Request.Header.Set("Content-Type", "application/json")

	handler.Subscribe(c)

	// RespondAppError with errors.NewValidationError goes through c.Error()
	// since it's not an apierr.Error type
	assert.NotEmpty(t, c.Errors, "Expected validation error recorded for invalid JSON")
}

// ---------------------------------------------------------------------------
// StreamingHandler.Connect — session validation
// ---------------------------------------------------------------------------

func TestStreamingHandler_Connect_InvalidSession(t *testing.T) {
	handler := rest.NewStreamingHandler(nil)

	connectBody := []map[string]interface{}{
		{"clientId": "nonexistent-client", "channel": "/meta/connect"},
	}
	bodyBytes, _ := json.Marshal(connectBody)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/cometd/1.0/connect", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")

	handler.Connect(c)

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

	var resp []map[string]interface{}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
	require.Len(t, resp, 1)
	assert.Equal(t, false, resp[0]["successful"])
}

// ---------------------------------------------------------------------------
// StreamingHandler.Disconnect — handles empty body gracefully
// ---------------------------------------------------------------------------

func TestStreamingHandler_Disconnect_EmptyBody(t *testing.T) {
	handler := rest.NewStreamingHandler(nil)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/cometd/1.0/disconnect", nil)

	handler.Disconnect(c)

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

	var resp []map[string]interface{}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
	require.Len(t, resp, 1)
	assert.Equal(t, true, resp[0]["successful"])
}
