package im_test

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

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

	"github.com/tidbcloud/AgentTeam/internal/api/dto"
	"github.com/tidbcloud/AgentTeam/internal/api/testkit"
	"github.com/tidbcloud/AgentTeam/internal/store/message"
	"github.com/tidbcloud/AgentTeam/internal/workflow/planmanage"
)

func TestWorkflowVisibleScopeRequiresActiveHumanAndPlan(t *testing.T) {
	runtime := testkit.StartRuntime(t)
	router := gin.New()
	runtime.RegisterRoutes(router)
	server := httptest.NewServer(router)
	defer server.Close()

	auth := testkit.RegisterUserForOrganizationTests(
		t,
		server.Client(),
		server.URL,
		"workflow-visible-scope@example.com",
		"workflow-visible-scope",
	)
	var workspaces []dto.WorkspaceResponse
	response := testkit.DoJSON(
		t,
		server.Client(),
		http.MethodGet,
		server.URL+"/api/v1/workspaces",
		nil,
		testkit.AuthHeaders(auth.AccessToken),
		&workspaces,
	)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, workspaces)
	workspace := workspaces[0]
	require.NotEmpty(t, workspace.MemberID)

	outsideAuth := testkit.RegisterUserForOrganizationTests(
		t,
		server.Client(),
		server.URL,
		"workflow-visible-scope-outside@example.com",
		"workflow-visible-scope-outside",
	)
	var outsideWorkspaces []dto.WorkspaceResponse
	response = testkit.DoJSON(
		t,
		server.Client(),
		http.MethodGet,
		server.URL+"/api/v1/workspaces",
		nil,
		testkit.AuthHeaders(outsideAuth.AccessToken),
		&outsideWorkspaces,
	)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, outsideWorkspaces)
	outsideWorkspace := outsideWorkspaces[0]
	require.NotEqual(t, workspace.ID, outsideWorkspace.ID)
	require.NotEmpty(t, outsideWorkspace.MemberID)

	plan, _, err := runtime.WorkflowPlan.Create(runtime.Ctx, planmanage.CreateInput{
		WorkspaceID:    workspace.ID,
		Key:            "visible_scope_gate",
		DisplayName:    "Visible scope gate",
		Source:         workflowVisibleScopePlanSource,
		Layout:         json.RawMessage(`{}`),
		Actor:          planmanage.Actor{Type: planmanage.ActorTypeHuman, ID: auth.User.ID, MemberID: workspace.MemberID},
		IdempotencyKey: "workflow-visible-scope-gate",
	})
	require.NoError(t, err)
	require.NotEmpty(t, plan.ScopeID)

	now := time.Now().UTC()
	const (
		agentMemberID    = "mem_workflow_visible_agent"
		inactiveMemberID = "mem_workflow_visible_inactive"
		orphanScopeID    = "wfs_workflow_visible_orphan"
	)
	_, err = runtime.Pool.Exec(runtime.Ctx, `
INSERT INTO members (
  id, workspace_id, type, display_name, status, created_at, updated_at
) VALUES
  ($1, $3, 'agent', 'Workflow Agent', 'active', $4, $4),
  ($2, $3, 'human', 'Inactive Human', 'archived', $4, $4)
`, agentMemberID, inactiveMemberID, workspace.ID, now)
	require.NoError(t, err)
	_, err = runtime.Pool.Exec(runtime.Ctx, `
INSERT INTO scopes (
  id, workspace_id, type, identity_key, status, created_by_member_id,
  created_at, updated_at
) VALUES ($1, $2, 'workflow', $3, 'active', $4, $5, $5)
`, orphanScopeID, workspace.ID, "workflow:orphan", workspace.MemberID, now)
	require.NoError(t, err)

	authorizer := message.NewSQLVisibleScopeAuthorizer(runtime.Pool)
	tests := []struct {
		name        string
		workspaceID string
		memberID    string
		scopeID     string
		want        bool
	}{
		{
			name:        "active Human reads resolved Plan scope",
			workspaceID: workspace.ID,
			memberID:    workspace.MemberID,
			scopeID:     plan.ScopeID,
			want:        true,
		},
		{
			name:        "orphan Workflow scope is denied",
			workspaceID: workspace.ID,
			memberID:    workspace.MemberID,
			scopeID:     orphanScopeID,
		},
		{
			name:        "Agent is denied",
			workspaceID: workspace.ID,
			memberID:    agentMemberID,
			scopeID:     plan.ScopeID,
		},
		{
			name:        "inactive Human is denied",
			workspaceID: workspace.ID,
			memberID:    inactiveMemberID,
			scopeID:     plan.ScopeID,
		},
		{
			name:        "active Human from another workspace is denied",
			workspaceID: workspace.ID,
			memberID:    outsideWorkspace.MemberID,
			scopeID:     plan.ScopeID,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			readable, authorizeErr := authorizer.CanReadVisibleScope(
				runtime.Ctx,
				test.workspaceID,
				test.memberID,
				test.scopeID,
			)
			require.NoError(t, authorizeErr)
			require.Equal(t, test.want, readable)
		})
	}
}

const workflowVisibleScopePlanSource = `apiVersion: loop.plan/v1
kind: PlanVersion
graph:
  entry: {nodeId: done, port: in}
  nodes:
  - id: done
    type: end
    end: {outcome: succeeded}
  edges: []
`
