package rest_test

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

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/gin-gonic/gin"
	"github.com/nexuscrm/backend/internal/bootstrap"
	"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"
)

// ---------------------------------------------------------------------------
// FlowHandler — unauthenticated paths (all flow handlers check user first)
// We can only test unauthenticated paths without mocking internal services.
// ---------------------------------------------------------------------------

func TestFlowHandler_ListInvocableFlows_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/actions/custom/flow", nil)

	assert.Panics(t, func() { handler.ListInvocableFlows(c) })
}

func TestFlowHandler_GetFlowSchema_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/actions/custom/flow/TestFlow", nil)
	c.Params = gin.Params{{Key: "name", Value: "TestFlow"}}

	assert.Panics(t, func() { handler.GetFlowSchema(c) })
}

func TestFlowHandler_ExecuteFlow_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/actions/custom/flow/TestFlow", bytes.NewReader([]byte(`{}`)))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Params = gin.Params{{Key: "name", Value: "TestFlow"}}

	assert.Panics(t, func() { handler.ExecuteFlow(c) })
}

func TestFlowHandler_CustomFlowAPI_HidesStandardActionFlowsFromList(t *testing.T) {
	gin.SetMode(gin.TestMode)

	db, sqlMock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()
	expectProtectedFlowNames(sqlMock, []string{"StandardActionOwnedFlow"}, []string{"Std_RelinkLeadChildren"})

	repo := &MockMetadataRepo{
		flowDefinitions: []*models.FlowDefinition{
			{DeveloperName: "StandardActionOwnedFlow", MasterLabel: "Standard Action Owned Flow", ActiveVersionId: "v-standard"},
			{DeveloperName: "standardactionownedflow", MasterLabel: "Standard Action Owned Flow Case Variant", ActiveVersionId: "v-standard-case"},
			{DeveloperName: "Std_RelinkLeadChildren", MasterLabel: "Standard Helper Flow", ActiveVersionId: "v-standard-helper"},
			{DeveloperName: "CustomInvocableFlow", MasterLabel: "Custom Invocable Flow", ActiveVersionId: "v-custom"},
			{DeveloperName: "DraftOnlyFlow", MasterLabel: "Draft Only Flow"},
		},
	}
	repo.On("GetAllSchemas", mock.Anything).Return([]*models.ObjectMetadata{}, nil)
	handler := rest.NewFlowHandler(nil, nil, metadata.NewMetadataService(repo, nil, nil), db)
	c, w := authenticatedFlowContext(http.MethodGet, "/actions/custom/flow", nil)

	handler.ListInvocableFlows(c)

	require.Equal(t, http.StatusOK, w.Code)
	body := w.Body.String()
	assert.Contains(t, body, "CustomInvocableFlow")
	assert.NotContains(t, body, "StandardActionOwnedFlow")
	assert.NotContains(t, body, "standardactionownedflow")
	assert.NotContains(t, body, "Std_RelinkLeadChildren")
	assert.NotContains(t, body, "DraftOnlyFlow")
	require.NoError(t, sqlMock.ExpectationsWereMet())
	repo.AssertExpectations(t)
}

func TestFlowHandler_CustomFlowAPI_AllowsCustomizedActionFlowsInList(t *testing.T) {
	gin.SetMode(gin.TestMode)

	db, sqlMock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()
	expectProtectedFlowNames(sqlMock, []string{"PackActionFlow"}, nil)
	expectProtectedFlowNames(sqlMock, []string{"PackActionFlow"}, nil)

	repo := &MockMetadataRepo{
		flowDefinitions: []*models.FlowDefinition{
			{DeveloperName: "PackActionFlow", MasterLabel: "Pack Action Flow", ActiveVersionId: "v-pack"},
			{DeveloperName: "CustomizedActionFlow", MasterLabel: "Customized Action Flow", ActiveVersionId: "v-customized-action"},
		},
		flowVersions: map[string]*models.FlowVersion{
			"v-customized-action": {Id: "v-customized-action"},
		},
	}
	repo.On("GetAllSchemas", mock.Anything).Return([]*models.ObjectMetadata{}, nil)
	handler := rest.NewFlowHandler(nil, nil, metadata.NewMetadataService(repo, nil, nil), db)
	c, w := authenticatedFlowContext(http.MethodGet, "/actions/custom/flow", nil)

	handler.ListInvocableFlows(c)

	require.Equal(t, http.StatusOK, w.Code)
	body := w.Body.String()
	assert.NotContains(t, body, "PackActionFlow")
	assert.Contains(t, body, "CustomizedActionFlow")

	schemaCtx, schemaRecorder := authenticatedFlowContext(http.MethodGet, "/actions/custom/flow/CustomizedActionFlow", nil)
	schemaCtx.Params = gin.Params{{Key: "name", Value: "CustomizedActionFlow"}}
	handler.GetFlowSchema(schemaCtx)

	require.Equal(t, http.StatusOK, schemaRecorder.Code)
	assert.Contains(t, schemaRecorder.Body.String(), "CustomizedActionFlow")
	require.NoError(t, sqlMock.ExpectationsWereMet())
	repo.AssertExpectations(t)
}

func TestFlowHandler_CustomFlowAPI_RejectsProtectedFlowsSchemaAndExecute(t *testing.T) {
	gin.SetMode(gin.TestMode)

	db, sqlMock, err := sqlmock.New()
	require.NoError(t, err)
	defer db.Close()
	for i := 0; i < 4; i++ {
		expectProtectedFlowNames(sqlMock, []string{"StandardActionOwnedFlow"}, []string{"Std_RelinkLeadChildren"})
	}

	handler := rest.NewFlowHandler(nil, nil, nil, db)

	for _, flowName := range []string{"standardactionownedflow", "std_relinkleadchildren"} {
		getCtx, getRecorder := authenticatedFlowContext(http.MethodGet, "/actions/custom/flow/"+flowName, nil)
		getCtx.Params = gin.Params{{Key: "name", Value: flowName}}
		handler.GetFlowSchema(getCtx)

		require.Equal(t, http.StatusNotFound, getRecorder.Code)
		assert.Contains(t, getRecorder.Body.String(), "Flow")

		postCtx, postRecorder := authenticatedFlowContext(
			http.MethodPost,
			"/actions/custom/flow/"+flowName,
			bytes.NewReader([]byte(`{"inputs":[{}]}`)),
		)
		postCtx.Params = gin.Params{{Key: "name", Value: flowName}}
		postCtx.Request.Header.Set("Content-Type", "application/json")
		handler.ExecuteFlow(postCtx)

		require.Equal(t, http.StatusNotFound, postRecorder.Code)
		assert.Contains(t, postRecorder.Body.String(), "Flow")
	}
	require.NoError(t, sqlMock.ExpectationsWereMet())
}

func authenticatedFlowContext(method, path string, body *bytes.Reader) (*gin.Context, *httptest.ResponseRecorder) {
	if body == nil {
		body = bytes.NewReader(nil)
	}
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest(method, path, body)
	c.Set(constants.ContextKeyUser, auth.UserSession{ID: "005000000000001", ProfileId: "00e000000000001"})
	return c, w
}

func expectProtectedFlowNames(mockDB sqlmock.Sqlmock, actionNames []string, packNames []string) {
	actionRows := sqlmock.NewRows([]string{"FlowDeveloperName"})
	for _, name := range actionNames {
		actionRows.AddRow(name)
	}
	mockDB.ExpectQuery(`SELECT FlowDeveloperName\s+FROM _System_StandardAction\s+WHERE FlowDeveloperName IS NOT NULL AND FlowDeveloperName <> ''\s+AND \(IsDeleted = 0 OR IsDeleted IS NULL\)\s+AND \(IsCustomized = 0 OR IsCustomized IS NULL\)`).
		WillReturnRows(actionRows)

	packRows := sqlmock.NewRows([]string{"DeveloperName"})
	for _, name := range packNames {
		packRows.AddRow(name)
	}
	mockDB.ExpectQuery(`SELECT fd\.DeveloperName\s+FROM FlowDefinition fd\s+JOIN Flow f ON f\.Id = fd\.ActiveVersionId AND f\.IsDeleted = 0\s+WHERE fd\.DeveloperName IS NOT NULL AND fd\.DeveloperName <> ''\s+AND fd\.IsDeleted = 0\s+AND fd\.IsCustomized = 0\s+AND f\.Source = \?`).
		WithArgs(bootstrap.FlowSourceStandard).
		WillReturnRows(packRows)
}

func TestFlowHandler_ListInterviews_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/sobjects/FlowInterview", nil)

	assert.Panics(t, func() { handler.ListInterviews(c) })
}

func TestFlowHandler_GetInterview_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/sobjects/FlowInterview/int001", nil)
	c.Params = gin.Params{{Key: "id", Value: "int001"}}

	assert.Panics(t, func() { handler.GetInterview(c) })
}

func TestFlowHandler_DeleteInterview_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("DELETE", "/sobjects/FlowInterview/int001", nil)
	c.Params = gin.Params{{Key: "id", Value: "int001"}}

	assert.Panics(t, func() { handler.DeleteInterview(c) })
}

func TestFlowHandler_ResumeInterview_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/actions/custom/resumeFlow", bytes.NewReader([]byte(`{}`)))
	c.Request.Header.Set("Content-Type", "application/json")

	assert.Panics(t, func() { handler.ResumeInterview(c) })
}

func TestFlowHandler_ListFlowDefinitions_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/tooling/sobjects/FlowDefinition", nil)

	assert.Panics(t, func() { handler.ListFlowDefinitions(c) })
}

func TestFlowHandler_GetFlowDefinition_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/tooling/sobjects/FlowDefinition/def001", nil)
	c.Params = gin.Params{{Key: "id", Value: "def001"}}

	assert.Panics(t, func() { handler.GetFlowDefinition(c) })
}

func TestFlowHandler_CreateFlowDefinition_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/tooling/sobjects/FlowDefinition", bytes.NewReader([]byte(`{}`)))
	c.Request.Header.Set("Content-Type", "application/json")

	assert.Panics(t, func() { handler.CreateFlowDefinition(c) })
}

func TestFlowHandler_UpdateFlowDefinition_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("PATCH", "/tooling/sobjects/FlowDefinition/def001", bytes.NewReader([]byte(`{}`)))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Params = gin.Params{{Key: "id", Value: "def001"}}

	assert.Panics(t, func() { handler.UpdateFlowDefinition(c) })
}

func TestFlowHandler_DeleteFlowDefinition_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("DELETE", "/tooling/sobjects/FlowDefinition/def001", nil)
	c.Params = gin.Params{{Key: "id", Value: "def001"}}

	assert.Panics(t, func() { handler.DeleteFlowDefinition(c) })
}

func TestFlowHandler_ListFlowVersions_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/tooling/sobjects/Flow", nil)

	assert.Panics(t, func() { handler.ListFlowVersions(c) })
}

func TestFlowHandler_GetFlowVersion_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("GET", "/tooling/sobjects/Flow/ver001", nil)
	c.Params = gin.Params{{Key: "id", Value: "ver001"}}

	assert.Panics(t, func() { handler.GetFlowVersion(c) })
}

func TestFlowHandler_CreateFlowVersion_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/tooling/sobjects/Flow", bytes.NewReader([]byte(`{}`)))
	c.Request.Header.Set("Content-Type", "application/json")

	assert.Panics(t, func() { handler.CreateFlowVersion(c) })
}

func TestFlowHandler_UpdateFlowVersion_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("PATCH", "/tooling/sobjects/Flow/ver001", bytes.NewReader([]byte(`{}`)))
	c.Request.Header.Set("Content-Type", "application/json")
	c.Params = gin.Params{{Key: "id", Value: "ver001"}}

	assert.Panics(t, func() { handler.UpdateFlowVersion(c) })
}

func TestFlowHandler_DeleteFlowVersion_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("DELETE", "/tooling/sobjects/Flow/ver001", nil)
	c.Params = gin.Params{{Key: "id", Value: "ver001"}}

	assert.Panics(t, func() { handler.DeleteFlowVersion(c) })
}

func TestFlowHandler_ActivateFlowVersion_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/tooling/sobjects/Flow/ver001/activate", nil)
	c.Params = gin.Params{{Key: "id", Value: "ver001"}}

	assert.Panics(t, func() { handler.ActivateFlowVersion(c) })
}

func TestFlowHandler_DeactivateFlowVersion_Unauthenticated(t *testing.T) {
	handler := rest.NewFlowHandler(nil, nil, nil)
	w := httptest.NewRecorder()
	c, _ := gin.CreateTestContext(w)
	c.Request = httptest.NewRequest("POST", "/tooling/sobjects/Flow/ver001/deactivate", nil)
	c.Params = gin.Params{{Key: "id", Value: "ver001"}}

	assert.Panics(t, func() { handler.DeactivateFlowVersion(c) })
}
