package contract

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

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	apperrors "veza-backend-api/internal/errors"
	"veza-backend-api/internal/handlers"
)

// INT-009: API Contract Tests
// These tests verify that API responses match the expected contract
// between frontend and backend, ensuring compatibility.

// APIResponse represents the standard API response envelope
type APIResponse struct {
	Success bool        `json:"success"`
	Data    interface{} `json:"data,omitempty"`
	Error   interface{} `json:"error,omitempty"`
}

// ErrorResponse represents the standard error response structure
type ErrorResponse struct {
	Code      int                     `json:"code"`
	Message   string                  `json:"message"`
	Details   []apperrors.ErrorDetail `json:"details,omitempty"`
	RequestID string                  `json:"request_id,omitempty"`
	Timestamp string                  `json:"timestamp"`
	Context   map[string]interface{}  `json:"context,omitempty"`
}

// PaginationData represents the standard pagination structure
type PaginationData struct {
	Page       int    `json:"page"`
	Limit      int    `json:"limit"`
	Total      int64  `json:"total"`
	TotalPages int    `json:"total_pages"`
	HasNext    bool   `json:"has_next"`
	HasPrev    bool   `json:"has_prev"`
	NextCursor string `json:"next_cursor,omitempty"`
	PrevCursor string `json:"prev_cursor,omitempty"`
}

// TestErrorResponseFormat verifies that error responses follow the standard format
// INT-009: Verify error response contract
func TestErrorResponseFormat(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	// Add request ID middleware for testing
	router.Use(func(c *gin.Context) {
		c.Set("request_id", "test-request-id")
		c.Next()
	})

	// Test endpoint that returns an error
	router.GET("/test-error", func(c *gin.Context) {
		appErr := apperrors.New(apperrors.ErrCodeValidation, "Test validation error")
		handlers.RespondWithAppError(c, appErr)
	})

	// Make request
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/test-error", nil)
	router.ServeHTTP(w, req)

	// Verify response
	assert.Equal(t, http.StatusBadRequest, w.Code)

	var response APIResponse
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	// Verify envelope structure
	assert.False(t, response.Success)
	assert.Nil(t, response.Data)
	assert.NotNil(t, response.Error)

	// Verify error structure
	errorBytes, _ := json.Marshal(response.Error)
	var errorResp ErrorResponse
	err = json.Unmarshal(errorBytes, &errorResp)
	require.NoError(t, err)

	// Verify error fields
	assert.Equal(t, int(apperrors.ErrCodeValidation), errorResp.Code)
	assert.Equal(t, "Test validation error", errorResp.Message)
	assert.Equal(t, "test-request-id", errorResp.RequestID)
	assert.NotEmpty(t, errorResp.Timestamp)

	// Verify timestamp is ISO 8601 (RFC3339)
	_, err = time.Parse(time.RFC3339, errorResp.Timestamp)
	assert.NoError(t, err, "Timestamp should be in RFC3339 format")
}

// TestSuccessResponseFormat verifies that success responses follow the standard format
// INT-009: Verify success response contract
func TestSuccessResponseFormat(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	// Test endpoint that returns success
	router.GET("/test-success", func(c *gin.Context) {
		handlers.RespondSuccess(c, http.StatusOK, gin.H{
			"message": "Test success",
			"data":    gin.H{"id": "123"},
		})
	})

	// Make request
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/test-success", nil)
	router.ServeHTTP(w, req)

	// Verify response
	assert.Equal(t, http.StatusOK, w.Code)

	var response APIResponse
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	// Verify envelope structure
	assert.True(t, response.Success)
	assert.NotNil(t, response.Data)
	assert.Nil(t, response.Error)
}

// TestPaginationFormat verifies that paginated responses follow the standard format
// INT-009: Verify pagination contract
func TestPaginationFormat(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	// Test endpoint that returns paginated data
	router.GET("/test-pagination", func(c *gin.Context) {
		pagination := handlers.BuildPaginationData(1, 20, 100)
		handlers.RespondSuccess(c, http.StatusOK, gin.H{
			"items":      []string{"item1", "item2"},
			"pagination": pagination,
		})
	})

	// Make request
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/test-pagination", nil)
	router.ServeHTTP(w, req)

	// Verify response
	assert.Equal(t, http.StatusOK, w.Code)

	var response APIResponse
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	// Verify envelope structure
	assert.True(t, response.Success)
	assert.NotNil(t, response.Data)

	// Extract pagination from data
	dataBytes, _ := json.Marshal(response.Data)
	var dataMap map[string]interface{}
	err = json.Unmarshal(dataBytes, &dataMap)
	require.NoError(t, err)

	paginationBytes, _ := json.Marshal(dataMap["pagination"])
	var pagination PaginationData
	err = json.Unmarshal(paginationBytes, &pagination)
	require.NoError(t, err)

	// Verify pagination fields
	assert.Equal(t, 1, pagination.Page)
	assert.Equal(t, 20, pagination.Limit)
	assert.Equal(t, int64(100), pagination.Total)
	assert.Equal(t, 5, pagination.TotalPages)
	assert.True(t, pagination.HasNext)
	assert.False(t, pagination.HasPrev)
}

// TestDateTimeFormat verifies that date/time fields use ISO 8601 format
// INT-009: Verify date/time format contract
func TestDateTimeFormat(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	// Test endpoint that returns a timestamp
	router.GET("/test-datetime", func(c *gin.Context) {
		now := time.Now().UTC()
		handlers.RespondSuccess(c, http.StatusOK, gin.H{
			"timestamp":  now.Format(time.RFC3339),
			"created_at": now.Format(time.RFC3339),
		})
	})

	// Make request
	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/test-datetime", nil)
	router.ServeHTTP(w, req)

	// Verify response
	assert.Equal(t, http.StatusOK, w.Code)

	var response APIResponse
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	// Extract data
	dataBytes, _ := json.Marshal(response.Data)
	var dataMap map[string]interface{}
	err = json.Unmarshal(dataBytes, &dataMap)
	require.NoError(t, err)

	// Verify timestamp format
	timestamp, ok := dataMap["timestamp"].(string)
	require.True(t, ok, "Timestamp should be a string")
	_, err = time.Parse(time.RFC3339, timestamp)
	assert.NoError(t, err, "Timestamp should be in RFC3339 format")

	createdAt, ok := dataMap["created_at"].(string)
	require.True(t, ok, "CreatedAt should be a string")
	_, err = time.Parse(time.RFC3339, createdAt)
	assert.NoError(t, err, "CreatedAt should be in RFC3339 format")
}

// TestAPIResponseEnvelope verifies that all responses use the APIResponse envelope
// INT-009: Verify API response envelope contract
func TestAPIResponseEnvelope(t *testing.T) {
	testCases := []struct {
		name           string
		handler        gin.HandlerFunc
		expectedStatus int
		expectSuccess  bool
	}{
		{
			name: "Success response",
			handler: func(c *gin.Context) {
				handlers.RespondSuccess(c, http.StatusOK, gin.H{"test": "data"})
			},
			expectedStatus: http.StatusOK,
			expectSuccess:  true,
		},
		{
			name: "Error response",
			handler: func(c *gin.Context) {
				appErr := apperrors.New(apperrors.ErrCodeNotFound, "Not found")
				handlers.RespondWithAppError(c, appErr)
			},
			expectedStatus: http.StatusNotFound,
			expectSuccess:  false,
		},
	}

	for _, tc := range testCases {
		t.Run(tc.name, func(t *testing.T) {
			gin.SetMode(gin.TestMode)
			router := gin.New()
			router.GET("/test", tc.handler)

			w := httptest.NewRecorder()
			req, _ := http.NewRequest("GET", "/test", nil)
			router.ServeHTTP(w, req)

			assert.Equal(t, tc.expectedStatus, w.Code)

			var response APIResponse
			err := json.Unmarshal(w.Body.Bytes(), &response)
			require.NoError(t, err)

			// Verify envelope structure
			assert.Equal(t, tc.expectSuccess, response.Success)

			if tc.expectSuccess {
				assert.NotNil(t, response.Data)
				assert.Nil(t, response.Error)
			} else {
				assert.Nil(t, response.Data)
				assert.NotNil(t, response.Error)
			}
		})
	}
}

// TestSnakeCaseNaming verifies that all JSON fields use snake_case
// INT-009: Verify naming convention contract
func TestSnakeCaseNaming(t *testing.T) {
	gin.SetMode(gin.TestMode)
	router := gin.New()

	router.GET("/test-naming", func(c *gin.Context) {
		handlers.RespondSuccess(c, http.StatusOK, gin.H{
			"user_id":    "123",
			"created_at": time.Now().UTC().Format(time.RFC3339),
			"first_name": "John",
			"last_name":  "Doe",
		})
	})

	w := httptest.NewRecorder()
	req, _ := http.NewRequest("GET", "/test-naming", nil)
	router.ServeHTTP(w, req)

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

	var response APIResponse
	err := json.Unmarshal(w.Body.Bytes(), &response)
	require.NoError(t, err)

	// Verify that JSON uses snake_case
	dataBytes, _ := json.Marshal(response.Data)
	var dataMap map[string]interface{}
	err = json.Unmarshal(dataBytes, &dataMap)
	require.NoError(t, err)

	// Check that keys are in snake_case
	assert.Contains(t, dataMap, "user_id")
	assert.Contains(t, dataMap, "created_at")
	assert.Contains(t, dataMap, "first_name")
	assert.Contains(t, dataMap, "last_name")
}
