package run_test

import (
	"context"
	"encoding/json"
	"fmt"
	"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/api/workflow/run"
	"github.com/tidbcloud/AgentTeam/internal/core/identifiers"
	"github.com/tidbcloud/AgentTeam/internal/coredb"
	runstore "github.com/tidbcloud/AgentTeam/internal/store/workflow/run"
	"github.com/tidbcloud/AgentTeam/internal/workflow/invocation"
	"github.com/tidbcloud/AgentTeam/internal/workflow/orchestrator"
)

// ---------------------------------------------------------------------------
// Cross-layer genuine full-chain coverage (moved OUT of internal/store/
// workflowrun so the store package never imports the API package — the
// genuine_* store tests were the only store->api imports). The harness lives
// in the API package (the legitimate dependency direction) and drives a REAL
// dispatcher failure persisted in the REAL store, then consumes it through the
// REAL registered authenticated Workflow Run events HTTP route (real auth,
// real workspace resolution, real store-backed service, real RunLogResponse/
// EventResponse DTOs).
// ---------------------------------------------------------------------------

type genuineRunFixture struct {
	WorkspaceID string
	RunID       string
	MemberID    string
	Now         time.Time
}

// genuineSeedRun seeds the real workflow store rows (workspace/member/
// invocation/run/activation/effect/run_accepted) so a real dispatcher failure
// can be driven and observed. When seedWorkspace is true the helper creates a
// fresh user/org/workspace/member; when false it reuses an existing workspace
// (the authenticated actor's personal workspace) and only links a member row.
// Returns the fixture plus the real StableChildID effect id.
func genuineSeedRun(t *testing.T, ctx context.Context, db coredb.QueryExecer, workspaceID string, suffix string, seedWorkspace bool) (genuineRunFixture, string) {
	t.Helper()
	now := time.Date(2026, 8, 17, 13, 0, 0, 123456000, time.UTC)
	fixture := genuineRunFixture{
		WorkspaceID: workspaceID, RunID: "wri_gen_" + suffix,
		MemberID: "mem_gen_" + suffix, Now: now,
	}
	userID, orgID := "usr_gen_"+suffix, "org_gen_"+suffix
	memberID := fixture.MemberID
	invocationID := "wiv_gen_" + suffix
	input := []byte(`{"seed":"start"}`)
	inputDigest := invocation.InputDigest(input)
	if seedWorkspace {
		genuineMustExec(t, ctx, db, `INSERT INTO users (id,email,name,display_name,password_hash,created_at,updated_at) VALUES ($1,$2,$3,$3,'hash',$4,$4)`, userID, suffix+"-gen@example.com", "gen-"+suffix, now)
		genuineMustExec(t, ctx, db, `INSERT INTO organizations (id,slug,name,created_by_user_id,created_at,updated_at) VALUES ($1,$2,$2,$3,$4,$4)`, orgID, "gen-"+suffix, userID, now)
		genuineMustExec(t, ctx, db, `INSERT INTO workspaces (id,organization_id,slug,name,created_at,updated_at) VALUES ($1,$2,$3,$3,$4,$4)`, workspaceID, orgID, "gen-"+suffix, now)
		genuineMustExec(t, ctx, db, `INSERT INTO workspace_placements (workspace_id,workspace_group_id,state,placement_epoch,created_at,updated_at) VALUES ($1,$2,'active',1,$3,$3)`, workspaceID, "group_gen_"+suffix, now)
		genuineMustExec(t, ctx, db, `INSERT INTO members (id,workspace_id,type,display_name,user_id,workspace_role,status,created_at,updated_at) VALUES ($1,$2,'human',$3,$4,'member','active',$5,$5)`, memberID, workspaceID, "gen-"+suffix, userID, now)
	}
	genuineMustExec(t, ctx, db, `
INSERT INTO workflow_invocations (
 id,workspace_id,binding_id,binding_revision_id,binding_revision_number,binding_source_id,binding_activation_number,
 plan_id,plan_version_id,workflow_name_snapshot,source_kind,source_occurrence_ref,source_occurred_at,
 input_json,input_digest,source_provenance_json,
 owner_user_id_snapshot,owner_member_id_snapshot,principal_member_id_snapshot,author_member_id_snapshot,
 invoked_by_member_id,invoked_by_actor_type,invoked_by_actor_id,idempotency_key,request_digest,accepted_at
) VALUES (
	$1,$2,NULL,NULL,NULL,NULL,NULL,$3,$4,$13,'manual',$5,$6,
	$7,$8,'{}',$9,$10,$10,$10,$10,'human',$9,$11,$12,$6
)`, invocationID, workspaceID, "pln_gen_"+suffix, "plv_gen_"+suffix, "manual:"+suffix, now,
		input, inputDigest, userID, memberID, "accept-"+suffix, fmt.Sprintf("%064x", 42), "Workflow "+suffix)
	genuineMustExec(t, ctx, db, `
INSERT INTO workflow_run_instances (
	 id,workspace_id,invocation_id,plan_id,plan_version_id,workflow_name_snapshot,status,
	 input_json,input_digest,state_json,
	 owner_user_id_snapshot,owner_member_id_snapshot,principal_member_id_snapshot,author_member_id_snapshot,
	 queued_at,revision,last_event_seq,runnable_at,created_at,updated_at
) VALUES (
	$1,$2,$3,$4,$5,$11,'running',$6,$7,'{}',$8,$9,$9,$9,$10,1,1,$10,$10,$10
)`, fixture.RunID, workspaceID, invocationID,
		"pln_gen_"+suffix, "plv_gen_"+suffix, input, inputDigest, userID, memberID, now, "Workflow "+suffix)
	genuineMustExec(t, ctx, db, `
INSERT INTO workflow_run_instance_events (id,workspace_id,run_id,seq,event_type,payload_json,created_at)
VALUES ($1,$2,$3,1,'run_accepted','{"status":"queued"}',$4)`, "wre_gen_"+suffix, workspaceID, fixture.RunID, now)

	effectID, err := orchestrator.StableChildID("wfx", fixture.RunID, "genuine-activation\x00message")
	require.NoError(t, err)
	activationID := "a00000000000000000001"
	requestJSON := `{"intent":{"activationId":"` + activationID + `","nodeId":"notify","kind":"send_message","callback":false,"retry":{"maxAttempts":1,"backoffNanos":1000000000}},"message":{"workspaceId":"` + workspaceID + `","planId":"pln_gen_` + suffix + `","planVersionId":"plv_gen_` + suffix + `","runId":"` + fixture.RunID + `","activationId":"` + activationID + `","effectId":"` + effectID + `","nodeId":"notify","sourceKind":"manual","ownerUserId":"usr_gen_` + suffix + `","authorMemberId":"` + memberID + `","clientMessageId":"workflow-run:` + effectID + `","content":"deliver","target":"#genuine"}}`
	genuineMustExec(t, ctx, db, `
INSERT INTO workflow_run_activations (
 id,workspace_id,run_id,logical_key,parent_activation_id,node_id,opcode,status,
 input_json,output_json,scope_context_json,selected_port,attempt_count,retry_at,wake_at,deadline_at,
 error_code,error_json,started_at,completed_at,created_at,updated_at
) VALUES ($4,$1,$2,CONCAT('cb/',$4),NULL,'notify','send_message','waiting',
 '{}',NULL,'{}',NULL,1,NULL,NULL,NULL,NULL,NULL,NULL,NULL,$3,$3)`,
		workspaceID, fixture.RunID, now, activationID)
	genuineMustExec(t, ctx, db, `
INSERT INTO workflow_run_effects (
 id,workspace_id,run_id,activation_id,callback_responder_agent_id,message_id,effect_kind,idempotency_key,status,
 request_json,result_json,attempt_count,retry_at,deadline_at,lease_owner,lease_token,lease_expires_at,
 error_code,error_json,delivered_at,completed_at,created_at,updated_at
) VALUES ($1,$2,$3,$4,NULL,NULL,'send_message',$7,'pending',
 $5,NULL,1,$6,NULL,NULL,NULL,NULL,NULL,NULL,NULL,NULL,$6,$6)`,
		effectID, workspaceID, fixture.RunID, activationID, requestJSON, now, "cb/"+effectID)
	return fixture, effectID
}

func genuineMustExec(t *testing.T, ctx context.Context, db coredb.QueryExecer, query string, args ...any) {
	t.Helper()
	_, err := db.Exec(ctx, query, args...)
	require.NoError(t, err)
}

// genuineFailingWriter returns a delivery error carrying an arbitrary SQL/secret
// cause so the real dispatcher resolves the effect as failed and persists the
// public effect_failed event.
type genuineFailingWriter struct{}

func (w *genuineFailingWriter) WriteRunMessage(context.Context, orchestrator.MessageRequest) (orchestrator.MessageResult, error) {
	return orchestrator.MessageResult{}, genuineJSONError("SELECT * FROM users WHERE password='hunter2' tenant=acme")
}

func (w *genuineFailingWriter) RecoverRunMessageReceipt(context.Context, orchestrator.MessageRequest, string) (orchestrator.MessageResult, error) {
	return orchestrator.MessageResult{}, genuineJSONError("receipt recovery unavailable password=hunter2")
}

func genuineJSONError(message string) error {
	return &genuineSecretError{message: message}
}

type genuineSecretError struct{ message string }

func (e *genuineSecretError) Error() string { return e.message }

// TestGenuineDispatcherFailureThroughRegisteredAuthenticatedHTTPRoute drives a
// REAL dispatcher failure (secret-bearing cause) persisted in the REAL
// workflow store, then crosses the REAL registered authenticated Workflow Run
// events route: real session auth, real workspace resolution, a real
// store-backed service, and the real RunLogResponse/EventResponse projection
// DTOs. It proves the reviewed failure contract survives while the arbitrary
// SQL/secret cause and operational keys never reach the wire, and that 404/403
// non-enumeration holds for a cross-tenant workspace. This replaces the
// test-only ServeMux in the former store genuine test.
func TestGenuineDispatcherFailureThroughRegisteredAuthenticatedHTTPRoute(t *testing.T) {
	runtime := StartRuntime(t)
	router := gin.New()
	runtime.RegisterRoutes(router)
	server := httptest.NewServer(router)
	defer server.Close()

	client := server.Client()
	actor, workspaceID := registerGenuineActor(t, client, server.URL)
	ctx := runtime.Ctx
	fixture, effectID := genuineSeedRun(t, ctx, runtime.Pool, workspaceID, "full_chain", false)

	// Drive the REAL dispatcher against a REAL store on the same runtime pool.
	store := runstore.New(runtime.Pool)
	writer := &genuineFailingWriter{}
	dispatcher, err := orchestrator.NewEffectDispatcher(store, writer, nil, nil, orchestrator.EffectDispatcherOptions{
		WorkerID: "w1", Now: func() time.Time { return fixture.Now },
	})
	require.NoError(t, err)
	outcome, err := dispatcher.DispatchOnce(ctx)
	require.NoError(t, err)
	require.Equal(t, 1, outcome.Claimed)
	require.Equal(t, 1, outcome.Resolved)

	// The real registered authenticated events route serves the durable rows.
	eventsURL := fmt.Sprintf("%s/api/v1/workspaces/%s/workflow/runs/%s/events", server.URL, workspaceID, fixture.RunID)
	response := DoJSON(t, client, http.MethodGet, eventsURL, nil, WorkspaceHeaders(actor.AccessToken, workspaceID), nil)
	require.Equal(t, http.StatusOK, response.StatusCode)
	var list dto.WorkflowRunEventListResponse
	require.NoError(t, json.NewDecoder(response.Body).Decode(&list))
	require.Greater(t, len(list.Items), 0)
	var text string
	found := false
	for _, entry := range list.Items {
		if entry.Type == "effect_failed" {
			raw, _ := json.Marshal(entry)
			text = string(raw)
			found = true
			break
		}
	}
	require.True(t, found, "the real dispatcher effect_failed event must be served over the registered authenticated route")
	require.Contains(t, text, `"effectId":"`+effectID+`"`)
	require.Contains(t, text, `"code":"message.delivery_failed"`)
	require.Contains(t, text, `"stage":"dispatch"`)
	require.Contains(t, text, `"path":"notify"`)
	require.Contains(t, text, "The message could not be delivered")
	require.Contains(t, text, `"retryable":true`)
	require.NotContains(t, text, "hunter2")
	require.NotContains(t, text, "password")
	require.NotContains(t, text, "tenant=acme")
	require.NotContains(t, text, "SELECT")
	require.NotContains(t, text, "leaseToken")

	// 404/403 non-enumeration: a cross-tenant workspace must not enumerate.
	crossTenant := DoJSON(t, client, http.MethodGet, fmt.Sprintf("%s/api/v1/workspaces/%s/workflow/runs/%s/events", server.URL, "wsp_other", fixture.RunID), nil, WorkspaceHeaders(actor.AccessToken, workspaceID), nil)
	require.NotEqual(t, http.StatusOK, crossTenant.StatusCode, "cross-tenant workspace must not enumerate run events")
}

// registerGenuineActor registers a real user through the public auth API and
// returns the authenticated actor plus the user's personal workspace.
func registerGenuineActor(t *testing.T, client *http.Client, baseURL string) (dto.AuthTokensResponse, string) {
	t.Helper()
	var actor dto.AuthTokensResponse
	response := DoJSON(t, client, http.MethodPost, baseURL+"/api/v1/auth/register", map[string]any{
		"email": "genuine-fullchain@example.com", "password": TestUserPassword, "name": "genuine-fullchain",
	}, nil, &actor)
	require.Equal(t, http.StatusOK, response.StatusCode)
	var workspaces []dto.WorkspaceResponse
	response = DoJSON(t, client, http.MethodGet, baseURL+"/api/v1/workspaces", nil, AuthHeaders(actor.AccessToken), &workspaces)
	require.Equal(t, http.StatusOK, response.StatusCode)
	require.NotEmpty(t, workspaces, "a registered user must have a personal workspace")
	return actor, workspaces[0].ID
}

// TestGenuineProducerContractPathThroughPublicDTOs drives the REAL dispatcher
// (a real producer) with REAL StableChildID effect/activation ids, persists the
// rows through the real workflow store, and consumes the same rows through the
// real public Run event/effect projection DTOs (RunLogResponse/EventResponse/
// EffectResponse), proving the valid producer context survives while the
// arbitrary SQL/secret cause and non-producer fields are redacted. Moved from
// internal/store/workflow/run so the store package never imports the API
// package.
func TestGenuineProducerContractPathThroughPublicDTOs(t *testing.T) {
	runtime := StartRuntime(t)
	ctx := runtime.Ctx
	fixture, effectID := genuineSeedRun(t, ctx, runtime.Pool, "genuine", "genuine", true)
	store := runstore.New(runtime.Pool)

	rawMember, err := identifiers.NewMemberID()
	require.NoError(t, err)
	require.True(t, identifiers.ValidMemberIDRaw(rawMember))
	_ = rawMember

	writer := &genuineFailingWriter{}
	dispatcher, err := orchestrator.NewEffectDispatcher(store, writer, nil, nil, orchestrator.EffectDispatcherOptions{
		WorkerID: "w1", Now: func() time.Time { return fixture.Now },
	})
	require.NoError(t, err)
	outcome, err := dispatcher.DispatchOnce(ctx)
	require.NoError(t, err)
	require.Equal(t, 1, outcome.Claimed)
	require.Equal(t, 1, outcome.Resolved)

	events, err := store.ListEvents(ctx, runstore.ListEventsParams{
		WorkspaceID: fixture.WorkspaceID, RunID: fixture.RunID, Limit: 100,
	})
	require.NoError(t, err)
	var persistedEvent *runstore.Event
	for index := range events {
		if events[index].Type == "effect_failed" {
			persistedEvent = &events[index]
			break
		}
	}
	require.NotNil(t, persistedEvent, "real dispatcher must persist an effect_failed event")
	require.Contains(t, string(persistedEvent.PayloadJSON), `"effectId":"`+effectID+`"`)
	require.Contains(t, string(persistedEvent.PayloadJSON), `"code":"message.delivery_failed"`)
	require.NotContains(t, string(persistedEvent.PayloadJSON), "hunter2")
	require.NotContains(t, string(persistedEvent.PayloadJSON), "leaseToken")

	// Consume the SAME row through the real public Run event/effect projection
	// DTOs (the same projector the registered routes use).
	for _, project := range []func(runstore.Event) []byte{
		func(e runstore.Event) []byte { return run.RunLogResponse(e).Details },
		func(e runstore.Event) []byte { return run.EventResponse(e).Payload },
	} {
		text := string(project(*persistedEvent))
		require.Contains(t, text, `"effectId":"`+effectID+`"`, "real StableChildID effect id must survive the public projection")
		require.Contains(t, text, `"code":"message.delivery_failed"`)
		require.Contains(t, text, "The message could not be delivered")
		require.NotContains(t, text, "hunter2")
		require.NotContains(t, text, "leaseToken")
	}

	effect, err := store.LoadEffect(ctx, fixture.WorkspaceID, fixture.RunID, effectID)
	require.NoError(t, err)
	require.Equal(t, runstore.EffectStatusFailed, effect.Status)
	require.Equal(t, "message.delivery_failed", effect.ErrorCode)
	require.NotNil(t, effect.ErrorJSON)
	require.NotContains(t, string(effect.ErrorJSON), "hunter2")
	require.NotContains(t, string(effect.ErrorJSON), "password")
	require.NotContains(t, string(effect.ErrorJSON), "tenant=acme")
	require.NotContains(t, string(effect.ErrorJSON), "SELECT")
}
