package run

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

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

	"github.com/tidbcloud/AgentTeam/internal/api"
	"github.com/tidbcloud/AgentTeam/internal/store/identity"
	"github.com/tidbcloud/AgentTeam/internal/store/storeerr"
	"github.com/tidbcloud/AgentTeam/internal/store/workspace"
	"github.com/tidbcloud/AgentTeam/internal/workflow/publicerr"
	"github.com/tidbcloud/AgentTeam/internal/workflow/runmanage"
)

// TestRunCrossTenantSelectorHTTPMatrix proves the real Run HTTP handler projects
// an identical 404 not-found response for a missing Run selector and a
// cross-tenant/forbidden Run selector across domain, raw store, and wrapped
// public failures: same status, code, problem, details, and public fields.
// A caller cannot tell whether a protected Run exists in another tenant or
// whether the selector resolved at all.
func TestRunCrossTenantSelectorHTTPMatrix(t *testing.T) {
	gin.SetMode(gin.TestMode)

	domainMissing := runSelectorHTTPResponse(t, runmanage.RequestError{
		Kind: storeerr.ErrNotFound, Code: runmanage.ErrorCodeNotFound, Message: "Workflow Run wri_secret is missing",
	})
	domainForbidden := runSelectorHTTPResponse(t, runmanage.RequestError{
		Kind: storeerr.ErrForbidden, Code: runmanage.ErrorCodeForbidden, Message: "Workflow Run wri_secret is forbidden",
	})
	rawMissing := runSelectorHTTPResponse(t, storeerr.NotFound("Workflow Run wri_secret is missing"))
	rawForbidden := runSelectorHTTPResponse(t, storeerr.Forbidden("Workflow Run wri_secret is forbidden"))
	wrappedMissing := runSelectorHTTPResponse(t, publicerr.WorkflowError{
		Kind: storeerr.ErrNotFound, Code: "run_not_found", Problem: "Workflow Run wri_secret is missing",
	})
	wrappedForbidden := runSelectorHTTPResponse(t, publicerr.WorkflowError{
		Kind: storeerr.ErrForbidden, Code: "run_forbidden", Problem: "Workflow Run wri_secret is forbidden",
	})

	responses := []*httptest.ResponseRecorder{
		domainMissing, domainForbidden, rawMissing, rawForbidden, wrappedMissing, wrappedForbidden,
	}
	for _, response := range responses {
		require.Equal(t, http.StatusNotFound, response.Code)
		require.Contains(t, response.Body.String(), `"code":"run_not_found"`)
		require.NotContains(t, response.Body.String(), "wri_secret")
	}
	for _, response := range responses[1:] {
		require.JSONEq(t, responses[0].Body.String(), response.Body.String())
	}
}

func runSelectorHTTPResponse(t *testing.T, selectorErr error) *httptest.ResponseRecorder {
	t.Helper()
	fake := &fakeService{err: selectorErr}
	handler := NewHandler(fake)

	router := gin.New()
	router.GET("/workflow/runs/:runId",
		func(ctx *gin.Context) {
			api.SetAuthenticatedUser(ctx, identity.AuthenticatedUser{User: identity.User{ID: "usr_00000000000001"}})
			api.SetCurrentWorkspace(ctx, workspace.Summary{WorkspaceID: "wsp_1", MemberID: "000000000001", MemberUserID: "usr_00000000000001", WorkspaceRole: "owner"})
		},
		handleWorkspace(handler.Get),
	)

	request := httptest.NewRequest(http.MethodGet, "/workflow/runs/wri_secret", http.NoBody)
	recorder := httptest.NewRecorder()
	router.ServeHTTP(recorder, request)

	var body map[string]any
	require.NoError(t, json.Unmarshal(recorder.Body.Bytes(), &body))
	require.Equal(t, "run_not_found", body["code"])
	require.Equal(t, "run", body["stage"])
	return recorder
}

// TestRunCrossTenantSelectorServiceMatrix proves the real Run classifier
// produces an identical api.ErrorResponse for missing and forbidden protected
// Run selectors at the service boundary.
func TestRunCrossTenantSelectorServiceMatrix(t *testing.T) {
	missing, ok := classifyError(runmanage.RequestError{
		Kind: storeerr.ErrNotFound, Code: runmanage.ErrorCodeNotFound, Message: "Workflow Run wri_secret is missing",
		Details: map[string]any{"runId": "wri_secret"},
	})
	require.True(t, ok)
	forbidden, ok := classifyError(runmanage.RequestError{
		Kind: storeerr.ErrForbidden, Code: runmanage.ErrorCodeForbidden, Message: "Workflow Run wri_secret is forbidden",
		Details: map[string]any{"runId": "wri_secret"},
	})
	require.True(t, ok)

	require.Equal(t, http.StatusNotFound, missing.Status)
	require.Equal(t, http.StatusNotFound, forbidden.Status)
	require.Equal(t, missing.Code, forbidden.Code)
	require.Equal(t, "run_not_found", missing.Code)
	require.Equal(t, missing.Message, forbidden.Message)
	require.Nil(t, missing.Details)
	require.Nil(t, forbidden.Details)
	require.Equal(t, missing.Stage, forbidden.Stage)
	require.Equal(t, "run", missing.Stage)
	require.Equal(t, missing.Path, forbidden.Path)
	require.Equal(t, missing.Hint, forbidden.Hint)
	require.Equal(t, missing.Retryable, forbidden.Retryable)
	require.Equal(t, missing.Diagnostics, forbidden.Diagnostics)
	require.False(t, strings.Contains(missing.Message, "wri_secret"))
}

// TestRunCrossTenantSelectorRawAndUnknownMatrix proves the real Run classifier
// collapses raw store and wrapped public selectors to the same byte-identical
// run_not_found contract and keeps unknown/internal failures fail-closed.
func TestRunCrossTenantSelectorRawAndUnknownMatrix(t *testing.T) {
	rawNotFound, ok := classifyError(storeerr.NotFound("Workflow Run wri_secret is missing"))
	require.True(t, ok)
	rawForbidden, ok := classifyError(storeerr.Forbidden("Workflow Run wri_secret is forbidden"))
	require.True(t, ok)
	wrapped, ok := classifyError(publicerr.WorkflowError{
		Kind: storeerr.ErrNotFound, Code: "run_not_found", Problem: "Workflow Run wri_secret is missing",
	})
	require.True(t, ok)

	for _, response := range []api.ErrorResponse{rawNotFound, rawForbidden, wrapped} {
		require.Equal(t, http.StatusNotFound, response.Status)
		require.Equal(t, "run_not_found", response.Code)
		require.Equal(t, "run", response.Stage)
		require.Nil(t, response.Details)
	}
	require.Equal(t, rawNotFound.Code, rawForbidden.Code)
	require.Equal(t, rawNotFound.Message, rawForbidden.Message)
	require.Equal(t, rawNotFound.Message, wrapped.Message)

	// Unknown/internal failures fail closed to a generic 500 with no leak.
	unknown, ok := classifyError(errors.New("raw repository: dial tcp: SELECT secret FROM runs"))
	require.True(t, ok)
	require.Equal(t, http.StatusInternalServerError, unknown.Status)
	require.Equal(t, "run_request_failed", unknown.Code)
	require.NotContains(t, unknown.Message, "secret")
	require.Nil(t, unknown.Details)
}
