package rest_test

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

	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/application/services"
	"github.com/nexuscrm/backend/internal/features/metadata"
	"github.com/nexuscrm/backend/internal/interfaces/rest"
	"github.com/nexuscrm/backend/pkg/auth"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"github.com/stretchr/testify/require"
)

// ---------------------------------------------------------------------------
// MetadataHandler.GetSchemas — returns schemas from metadata service
// ---------------------------------------------------------------------------

func TestMetadataHandler_GetSchemas(t *testing.T) {
	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/metadata/objects", nil)

	schemas := []*models.ObjectMetadata{
		{APIName: "Account", Label: "Account"},
		{APIName: "Contact", Label: "Contact"},
	}
	mockMetaRepo.On("GetAllSchemas", mock.Anything).Return(schemas, nil)

	handler.GetSchemas(c)

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

	var resp map[string]interface{}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
	sobjects, ok := resp["sobjects"]
	assert.True(t, ok)
	assert.NotNil(t, sobjects)
}

// ---------------------------------------------------------------------------
// MetadataHandler.GetSchema — returns effective schema
// ---------------------------------------------------------------------------

func TestMetadataHandler_GetSchema_NotFound(t *testing.T) {
	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/metadata/objects/nonexistent", nil)
	c.Params = gin.Params{{Key: "apiName", Value: "nonexistent"}}
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})

	mockMetaRepo.On("GetSchemaByAPIName", mock.Anything, "nonexistent").Return(nil, nil).Maybe()
	mockMetaRepo.On("GetAllSchemas", mock.Anything).Return([]*models.ObjectMetadata{}, nil).Maybe()

	handler.GetSchema(c)

	// RespondAppError with appErrors.NotFoundError goes through c.Error() in test mode
	// because it's not an apierr.Error. Either gets 404 or error is recorded.
	if w.Code == http.StatusOK {
		assert.NotEmpty(t, c.Errors, "Expected error to be recorded for not-found schema")
	} else {
		assert.Equal(t, http.StatusNotFound, w.Code)
	}
}

// ---------------------------------------------------------------------------
// MetadataHandler.CreateSchema — validation (appErrors go through c.Error())
// ---------------------------------------------------------------------------

func TestMetadataHandler_CreateSchema_InvalidJSON(t *testing.T) {
	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

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

	handler.CreateSchema(c)

	// appErrors.ValidationError goes through c.Error() (not apierr.Error)
	assert.NotEmpty(t, c.Errors, "Expected validation error for invalid JSON")
}

func TestMetadataHandler_CreateSchema_MissingNameAndLabel(t *testing.T) {
	body := map[string]interface{}{
		"custom": true,
	}
	bodyBytes, _ := json.Marshal(body)

	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

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

	handler.CreateSchema(c)

	assert.NotEmpty(t, c.Errors, "Expected validation error for missing name and label")
}

// ---------------------------------------------------------------------------
// MetadataHandler.UpdateSchema — validation
// ---------------------------------------------------------------------------

func TestMetadataHandler_UpdateSchema_InvalidJSON(t *testing.T) {
	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("PATCH", "/metadata/objects/account", bytes.NewReader([]byte(`{bad`)))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Params = gin.Params{{Key: "apiName", Value: "account"}}

	handler.UpdateSchema(c)

	assert.NotEmpty(t, c.Errors, "Expected validation error for invalid JSON")
}

func TestMetadataHandler_RefreshCache(t *testing.T) {
	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/metadata/refresh-cache", nil)

	mockMetaRepo.On("GetAllSchemas", mock.Anything).Return([]*models.ObjectMetadata{}, nil).Once()

	handler.RefreshCache(c)

	assert.Equal(t, http.StatusOK, w.Code)
	var resp map[string]interface{}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
	assert.Equal(t, true, resp["success"])
	mockMetaRepo.AssertExpectations(t)
}

// ---------------------------------------------------------------------------
// MetadataHandler.GetFieldTypes — no service wiring needed
// ---------------------------------------------------------------------------

func TestMetadataHandler_GetFieldTypes(t *testing.T) {
	handler := rest.NewMetadataHandler(nil)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/metadata/fieldtypes", nil)

	handler.GetFieldTypes(c)

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

	var resp map[string]interface{}
	require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
	fieldTypes, ok := resp["fieldTypes"]
	assert.True(t, ok)
	assert.NotNil(t, fieldTypes)
}

// ---------------------------------------------------------------------------
// MetadataHandler.CreateField — validation (appErrors go through c.Error())
// ---------------------------------------------------------------------------

func TestMetadataHandler_CreateField_InvalidJSON(t *testing.T) {
	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/metadata/objects/account/fields", bytes.NewReader([]byte(`{bad`)))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Params = gin.Params{{Key: "apiName", Value: "account"}}

	handler.CreateField(c)

	assert.NotEmpty(t, c.Errors, "Expected validation error for invalid JSON")
}

func TestMetadataHandler_CreateField_MissingObjectAndField(t *testing.T) {
	body := map[string]interface{}{
		"type": "string",
	}
	bodyBytes, _ := json.Marshal(body)

	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/metadata/objects//fields", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Params = gin.Params{{Key: "apiName", Value: ""}}

	handler.CreateField(c)

	assert.NotEmpty(t, c.Errors, "Expected validation error for missing object API name")
}

func TestMetadataHandler_CreateField_MissingAPINameAndLabel(t *testing.T) {
	body := map[string]interface{}{
		"type":            "string",
		"object_api_name": "account",
	}
	bodyBytes, _ := json.Marshal(body)

	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/metadata/objects/account/fields", bytes.NewReader(bodyBytes))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Params = gin.Params{{Key: "apiName", Value: "account"}}

	handler.CreateField(c)

	assert.NotEmpty(t, c.Errors, "Expected validation error for missing API name and label")
}

// ---------------------------------------------------------------------------
// MetadataHandler.UpdateField — validation
// ---------------------------------------------------------------------------

func TestMetadataHandler_UpdateField_InvalidJSON(t *testing.T) {
	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("PATCH", "/metadata/objects/account/fields/myField", bytes.NewReader([]byte(`{bad`)))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Params = gin.Params{
		{Key: "apiName", Value: "account"},
		{Key: "fieldApiName", Value: "myField"},
	}

	handler.UpdateField(c)

	assert.NotEmpty(t, c.Errors, "Expected validation error for invalid JSON")
}

// ---------------------------------------------------------------------------
// MetadataHandler.BatchCreateFields — validation
// ---------------------------------------------------------------------------

func TestMetadataHandler_BatchCreateFields_InvalidJSON(t *testing.T) {
	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/metadata/objects/account/fields/batch", bytes.NewReader([]byte(`not json`)))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Params = gin.Params{{Key: "apiName", Value: "account"}}

	handler.BatchCreateFields(c)

	assert.NotEmpty(t, c.Errors, "Expected validation error for invalid JSON")
}

// ---------------------------------------------------------------------------
// MetadataHandler.CreateValidationRule — validation
// ---------------------------------------------------------------------------

func TestMetadataHandler_CreateValidationRule_InvalidJSON(t *testing.T) {
	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

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

	handler.CreateValidationRule(c)

	assert.NotEmpty(t, c.Errors, "Expected validation error for invalid JSON")
}

// ---------------------------------------------------------------------------
// MetadataHandler.UpdateValidationRule — validation
// ---------------------------------------------------------------------------

func TestMetadataHandler_UpdateValidationRule_InvalidJSON(t *testing.T) {
	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("PATCH", "/metadata/validation-rules/vr1", bytes.NewReader([]byte(`{bad`)))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Params = gin.Params{{Key: "id", Value: "vr1"}}

	handler.UpdateValidationRule(c)

	assert.NotEmpty(t, c.Errors, "Expected validation error for invalid JSON")
}

// ---------------------------------------------------------------------------
// MetadataHandler.GetValidationRules — validation
// ---------------------------------------------------------------------------

func TestMetadataHandler_GetValidationRules_MissingObjectName(t *testing.T) {
	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/metadata/validation-rules", nil)

	handler.GetValidationRules(c)

	assert.NotEmpty(t, c.Errors, "Expected validation error for missing object API name")
}

// ---------------------------------------------------------------------------
// MetadataHandler.DeployRequest — validation (no file)
// ---------------------------------------------------------------------------

func TestMetadataHandler_DeployRequest_NoFile(t *testing.T) {
	mockMetaRepo := new(MockMetadataRepo)
	metaSvc := metadata.NewMetadataService(mockMetaRepo, nil, nil)
	svcMgr := &services.ServiceManager{Metadata: metaSvc}

	handler := rest.NewMetadataHandler(svcMgr)

	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/metadata/deployRequest", nil)
	c.Request.Header.Set("Content-Type", "multipart/form-data")
	c.Set(constants.ContextKeyUser, auth.UserSession{
		ID:        "user1",
		ProfileId: constants.ProfileSystemAdmin,
	})

	handler.DeployRequest(c)

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