package plan

import (
	"context"
	"encoding/json"
	"errors"
	"net/http"
	"testing"

	"github.com/stretchr/testify/require"

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

type detailPlanService struct {
	Service
	plan planmanage.Plan
}

type metadataPlanService struct {
	detailPlanService
	updateCalls int
}

func (service *metadataPlanService) UpdateMetadata(_ context.Context, input planmanage.UpdateMetadataInput) (planmanage.Plan, error) {
	service.updateCalls++
	if input.DisplayName != nil {
		service.plan.DisplayName = *input.DisplayName
	}
	return service.plan, nil
}

func (service detailPlanService) Get(_ context.Context, workspaceID, planID string) (planmanage.Plan, error) {
	if workspaceID != service.plan.WorkspaceID || planID != service.plan.ID {
		return planmanage.Plan{}, errors.New("unexpected Plan identity")
	}
	return service.plan, nil
}

type managementResolvingGuard struct {
	teamID, teamWorkflowID string
	deleted, linked        bool
	err                    error
	calls                  [][2]string
}

func (*managementResolvingGuard) RequireUnlinkedPlan(context.Context, string, string) error {
	return nil
}

func (resolver *managementResolvingGuard) ResolvePlanManagement(
	_ context.Context,
	workspaceID string,
	planID string,
) (string, string, bool, bool, error) {
	resolver.calls = append(resolver.calls, [2]string{workspaceID, planID})
	return resolver.teamID, resolver.teamWorkflowID, resolver.deleted, resolver.linked, resolver.err
}

func TestWorkflowPlanDetailReturnsOptionalTeamManagement(t *testing.T) {
	tests := []struct {
		name         string
		deleted      bool
		linked       bool
		wantRelation string
		wantOmitted  bool
	}{
		{name: "live Team Workflow", linked: true, wantRelation: "live"},
		{name: "deleted Team Workflow tombstone", linked: true, deleted: true, wantRelation: "deleted"},
		{name: "unlinked workspace Plan", wantOmitted: true},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			plan := planmanage.Plan{ID: "pln_1", WorkspaceID: "wsp_1", Key: "release", Revision: 4}
			resolver := &managementResolvingGuard{
				teamID: "team_1", teamWorkflowID: "twf_1",
				deleted: test.deleted, linked: test.linked,
			}
			handler := newRouteHandlers(detailPlanService{plan: plan}, resolver).detail
			response, err := handler.Get(guardedPlanRequest(t, plan.ID, ""))
			require.NoError(t, err)
			require.Equal(t, [][2]string{{"wsp_1", "pln_1"}}, resolver.calls)

			body, ok := response.Body.(dto.WorkflowPlanResponse)
			require.True(t, ok)
			encoded, err := json.Marshal(body)
			require.NoError(t, err)
			if test.wantOmitted {
				require.Nil(t, body.Management)
				require.NotContains(t, string(encoded), `"management"`)
				return
			}
			require.Equal(t, &dto.WorkflowPlanManagementResponse{
				Kind: "team_workflow", TeamID: "team_1", TeamWorkflowID: "twf_1",
				RelationState: test.wantRelation,
			}, body.Management)
			var document map[string]any
			require.NoError(t, json.Unmarshal(encoded, &document))
			require.Equal(t, map[string]any{
				"kind": "team_workflow", "teamId": "team_1", "teamWorkflowId": "twf_1",
				"relationState": test.wantRelation,
			}, document["management"])
		})
	}
}

func TestWorkflowPlanDetailPropagatesManagementLookupFailure(t *testing.T) {
	plan := planmanage.Plan{ID: "pln_1", WorkspaceID: "wsp_1", Key: "release", Revision: 4}
	want := errors.New("management lookup failed")
	resolver := &managementResolvingGuard{err: want}
	handler := newRouteHandlers(detailPlanService{plan: plan}, resolver).detail

	_, err := handler.Get(guardedPlanRequest(t, plan.ID, ""))
	require.ErrorIs(t, err, want)
}

func TestWorkflowPlanMetadataMutationRequiresCreatorForOrdinaryPlans(t *testing.T) {
	plan := planmanage.Plan{
		ID: "pln_1", WorkspaceID: "wsp_1", Key: "release", Revision: 4,
		CreatedBy: planmanage.Actor{MemberID: "mem_creator"},
	}
	service := &metadataPlanService{detailPlanService: detailPlanService{plan: plan}}
	resolver := &managementResolvingGuard{}
	handler := newRouteHandlers(service, resolver).detail
	request := guardedPlanRequest(t, plan.ID, "")
	request.Workspace.MemberID = "mem_viewer"
	displayName := "changed"

	_, err := handler.Update(api.WorkspaceScopedJSON[dto.WorkflowPlanUpdateRequest]{
		WorkspaceScopedRequest: request,
		Body:                   dto.WorkflowPlanUpdateRequest{DisplayName: &displayName},
	})
	response, ok := classifyError(err)
	require.True(t, ok)
	require.Equal(t, http.StatusForbidden, response.Status)
	require.Equal(t, workflowPlanCreatorRequiredCode, response.Code)
	require.Equal(t, "plan", response.Stage)
	require.Zero(t, service.updateCalls)

	request.Workspace.MemberID = "mem_creator"
	_, err = handler.Update(api.WorkspaceScopedJSON[dto.WorkflowPlanUpdateRequest]{
		WorkspaceScopedRequest: request,
		Body:                   dto.WorkflowPlanUpdateRequest{DisplayName: &displayName},
	})
	require.NoError(t, err)
	require.Equal(t, 1, service.updateCalls)
}

func TestWorkflowPlanMetadataMutationPreservesTeamWorkflowAuthority(t *testing.T) {
	plan := planmanage.Plan{
		ID: "pln_team", WorkspaceID: "wsp_1", Key: "release", Revision: 4,
		CreatedBy: planmanage.Actor{MemberID: "mem_creator"},
	}
	service := &metadataPlanService{detailPlanService: detailPlanService{plan: plan}}
	resolver := &managementResolvingGuard{linked: true}
	handler := newRouteHandlers(service, resolver).detail
	request := guardedPlanRequest(t, plan.ID, "")
	request.Workspace.MemberID = "mem_team_author"
	displayName := "changed"

	_, err := handler.Update(api.WorkspaceScopedJSON[dto.WorkflowPlanUpdateRequest]{
		WorkspaceScopedRequest: request,
		Body:                   dto.WorkflowPlanUpdateRequest{DisplayName: &displayName},
	})
	require.NoError(t, err)
	require.Equal(t, 1, service.updateCalls)
}
