package approvalapp

import (
	"context"
	"encoding/json"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/internal/core/runeffect"
	"github.com/tidbcloud/AgentTeam/internal/service/agentapproval"
	"github.com/tidbcloud/AgentTeam/internal/store/agent"
	"github.com/tidbcloud/AgentTeam/internal/store/app"
	"github.com/tidbcloud/AgentTeam/internal/store/approval"
)

type fakeAgentManagementConsumer struct{}

func (fakeAgentManagementConsumer) Submit(context.Context, agent.AuthenticatedAgent, agentapproval.SubmitInput) (agentapproval.Result, error) {
	return agentapproval.Result{}, nil
}
func (fakeAgentManagementConsumer) Decide(context.Context, approval.DecideParams) (agentapproval.DecisionResult, error) {
	return agentapproval.DecisionResult{}, nil
}

func newAgentManagementHandlerForTest() *agentManagementSubjectHandler {
	return &agentManagementSubjectHandler{approvals: &approval.Store{}, service: fakeAgentManagementConsumer{}, projector: &Projector{}}
}

func TestAuthenticatedAgentPreservesMachineIdentity(t *testing.T) {
	require.Equal(t, agent.AuthenticatedAgent{
		WorkspaceID: "ws_1",
		AgentID:     "agt_1",
		MemberID:    "mem_1",
		MachineID:   "mac_1",
	}, authenticatedAgent(SubjectPrincipal{
		WorkspaceID: "ws_1",
		PrincipalID: "agt_1",
		MemberID:    "mem_1",
		MachineID:   "mac_1",
	}))
}

func TestAgentManagementValidate(t *testing.T) {
	handler := newAgentManagementHandlerForTest()
	validPayload, err := json.Marshal(agentapproval.Payload{SchemaVersion: agentapproval.PayloadSchemaVersion, Action: agentapproval.ActionCreate, Applicant: agentapproval.Applicant{AgentID: "agt", MemberID: "mem"}})
	require.NoError(t, err)
	tests := []struct {
		name     string
		instance approval.Instance
		wantErr  bool
	}{
		{"wrong subject type", approval.Instance{SubjectType: "other", Payload: validPayload}, true},
		{"empty payload", approval.Instance{SubjectType: agentapproval.SubjectType}, true},
		{"bad json", approval.Instance{SubjectType: agentapproval.SubjectType, Payload: []byte("{not json")}, true},
		{"unsupported schema version", approval.Instance{SubjectType: agentapproval.SubjectType, Payload: []byte(`{"schemaVersion":99}`)}, true},
		{"valid", approval.Instance{SubjectType: agentapproval.SubjectType, Payload: validPayload}, false},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			err := handler.Validate(context.Background(), tt.instance)
			if tt.wantErr {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
			}
		})
	}
}

func TestAgentManagementPresentPendingInvariants(t *testing.T) {
	handler := newAgentManagementHandlerForTest()
	payload, err := json.Marshal(agentapproval.Payload{SchemaVersion: agentapproval.PayloadSchemaVersion, Action: agentapproval.ActionCreate, Owner: agentapproval.OwnerRef{MemberID: "owner"}, Applicant: agentapproval.Applicant{AgentID: "agt"}})
	require.NoError(t, err)
	validInstance := approval.Instance{ID: "inst", SubjectType: agentapproval.SubjectType, State: approval.StatePending, Version: 1, Payload: payload}
	goodAssignment := approval.Assignment{ID: "asg", ApprovalInstanceID: "inst", ApproverMemberID: "owner", Status: approval.AssignmentPending}
	tests := []struct {
		name        string
		instance    approval.Instance
		assignments []approval.Assignment
	}{
		{"wrong subject type", approval.Instance{ID: "inst", SubjectType: "other", State: approval.StatePending, Version: 1, Payload: payload}, []approval.Assignment{goodAssignment}},
		{"not pending state", approval.Instance{ID: "inst", SubjectType: agentapproval.SubjectType, State: approval.StateApproved, Version: 1, Payload: payload}, []approval.Assignment{goodAssignment}},
		{"zero version", approval.Instance{ID: "inst", SubjectType: agentapproval.SubjectType, State: approval.StatePending, Version: 0, Payload: payload}, []approval.Assignment{goodAssignment}},
		{"zero assignments", validInstance, nil},
		{"two assignments", validInstance, []approval.Assignment{goodAssignment, goodAssignment}},
		{"assignment wrong instance", validInstance, []approval.Assignment{{ID: "asg", ApprovalInstanceID: "other", ApproverMemberID: "owner", Status: approval.AssignmentPending}}},
		{"assignment wrong status", validInstance, []approval.Assignment{{ID: "asg", ApprovalInstanceID: "inst", ApproverMemberID: "owner", Status: approval.AssignmentApproved}}},
		{"assignment wrong owner", validInstance, []approval.Assignment{{ID: "asg", ApprovalInstanceID: "inst", ApproverMemberID: "someone-else", Status: approval.AssignmentPending}}},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			_, err := handler.PresentPending(context.Background(), SubmitResult{Instance: tt.instance, Assignments: tt.assignments})
			require.Error(t, err)
		})
	}
}

func TestPendingAgentManagementCardContent(t *testing.T) {
	instance := approval.Instance{ID: "appr_1", Version: 2}
	assignment := approval.Assignment{ID: "asg_1", ApproverMemberID: "mem_owner"}
	payload := agentapproval.Payload{Action: agentapproval.ActionCreate, Applicant: agentapproval.Applicant{AgentID: "agt_1"}, Create: &agentapproval.CreateProposal{Name: "new-agent", MachineID: "mac_1", Runtime: "codex"}}
	content, err := pendingAgentManagementCardContent(instance, assignment, payload, "Workspace Owner")
	require.NoError(t, err)
	require.Equal(t, app.AppNotificationContentInteractiveCard, content.Kind)
	var card map[string]any
	require.NoError(t, json.Unmarshal(content.CardJSON, &card))
	require.Equal(t, "approval_action", card["template"])
	require.Equal(t, approval.StatePending, card["status"])
	approvalObj, ok := card["approval"].(map[string]any)
	require.True(t, ok)
	require.Equal(t, "appr_1", approvalObj["id"])
	require.Equal(t, float64(2), approvalObj["version"])
	require.Equal(t, "asg_1", approvalObj["assignmentId"])
	require.Equal(t, []any{"Workspace Owner"}, approvalObj["currentProcessorNames"])
	actions, ok := card["actions"].([]any)
	require.True(t, ok)
	require.Len(t, actions, 2)
}

func TestAgentManagementProcessingCardContent(t *testing.T) {
	instance := approval.Instance{
		ID: "appr_1", Version: 3, State: approval.StateApproved,
	}
	payload := agentapproval.Payload{
		Action:    agentapproval.ActionCreate,
		Applicant: agentapproval.Applicant{AgentID: "agt_1"},
		Create: &agentapproval.CreateProposal{
			Name: "new-agent", MachineID: "mac_1", Runtime: "codex",
		},
	}
	content, err := agentManagementDecisionCardContent(
		instance, "asg_1", payload, agentManagementCardStatusProcessing, "Workspace Owner",
	)
	require.NoError(t, err)
	require.Equal(t, app.AppNotificationContentInteractiveCard, content.Kind)

	var card map[string]any
	require.NoError(t, json.Unmarshal(content.CardJSON, &card))
	require.Equal(t, "approval_action", card["template"])
	require.Equal(t, agentManagementCardStatusProcessing, card["status"])
	require.Empty(t, card["actions"])
	require.Equal(t, map[string]any{
		"action": agentapproval.ActionCreate,
		"summary": map[string]any{
			"name": "new-agent", "machineId": "mac_1", "runtime": "codex",
		},
	}, card["proposal"])
	approvalObject := card["approval"].(map[string]any)
	require.Equal(t, "appr_1", approvalObject["id"])
	require.Equal(t, float64(3), approvalObject["version"])
	require.Equal(t, "asg_1", approvalObject["assignmentId"])
	require.Equal(t, "Workspace Owner", approvalObject["processorName"])
}

func TestApprovalProjectionEffect(t *testing.T) {
	require.Equal(t,
		runeffect.MessageUpdateEffect("btn_processing", "msg_processing"),
		approvalProjectionEffect(app.AppNotificationResult{
			NotificationID: "btn_processing", MessageID: "msg_processing",
			Status: app.AppNotificationStatusActive,
		}),
	)
	require.Equal(t,
		runeffect.NotificationResolvedEffect("btn_resolved", "msg_resolved"),
		approvalProjectionEffect(app.AppNotificationResult{
			NotificationID: "btn_resolved", MessageID: "msg_resolved",
			Status: app.AppNotificationStatusResolved,
		}),
	)
	// Generic subject presenters historically omit Status in focused handler
	// fakes; their terminal effect remains resolved.
	require.Equal(t,
		runeffect.NotificationResolvedEffect("btn_generic", "msg_generic"),
		approvalProjectionEffect(app.AppNotificationResult{
			NotificationID: "btn_generic", MessageID: "msg_generic",
		}),
	)
}
