package exec

import (
	"context"
	"net/http"
	"testing"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/apiclient"
)

type fakeRoleRuntime struct {
	listResult roleListResult
	getResult  roleGetResult
	request    roleRequestGetResult
	mutation   roleRequestResponse

	updateInput roleUpdateRequest
}

func (fake *fakeRoleRuntime) list(context.Context, roleListRequest) (roleListResult, error) {
	return fake.listResult, nil
}
func (fake *fakeRoleRuntime) get(context.Context, roleGetRequest) (roleGetResult, error) {
	return fake.getResult, nil
}
func (fake *fakeRoleRuntime) requestGet(context.Context, roleRequestGetRequest) (roleRequestGetResult, error) {
	return fake.request, nil
}
func (fake *fakeRoleRuntime) create(context.Context, roleCreateRequest) (roleRequestResponse, error) {
	return fake.mutation, nil
}
func (fake *fakeRoleRuntime) update(_ context.Context, input roleUpdateRequest) (roleRequestResponse, error) {
	fake.updateInput = input
	return fake.mutation, nil
}
func (fake *fakeRoleRuntime) delete(context.Context, roleDeleteRequest) (roleRequestResponse, error) {
	return fake.mutation, nil
}
func (fake *fakeRoleRuntime) apply(context.Context, roleApplyRequest) (roleRequestResponse, error) {
	return fake.mutation, nil
}

func TestRoleHandlersReturnTypedResultsAndNormalizeNilList(t *testing.T) {
	fake := &fakeRoleRuntime{
		listResult: roleListResult{Roles: nil},
		getResult: roleGetResult{Role: roleDetail{
			RoleID: "role_1", Name: "Reviewer", Definition: "Review",
			Visibility: "private", RoleVersionID: "rlv_1",
		}},
		request: roleRequestGetResult{
			RequestID: "req_1", Action: "apply", Status: "failed",
			AgentID: "agt_target", Failure: &roleFailure{Code: "not_found", Message: "Role not found"},
		},
		mutation: roleRequestResponse{ApprovalID: "appr_1", Status: "pending"},
	}
	handler := roleHandler{remote: fake}
	list, err := handler.list(context.Background(), actionScopedJSON[roleListRequest]{})
	requireRoleHandlerImplemented(t, err)
	require.NotNil(t, list.Data.Roles)
	require.Empty(t, list.Data.Roles)
	get, err := handler.get(context.Background(), actionScopedJSON[roleGetRequest]{
		Body: roleGetRequest{RoleID: "role_1"},
	})
	requireRoleHandlerImplemented(t, err)
	require.Equal(t, "role_1", get.Data.Role.RoleID)
	request, err := handler.requestGet(context.Background(), actionScopedJSON[roleRequestGetRequest]{
		Body: roleRequestGetRequest{RequestID: "req_1"},
	})
	requireRoleHandlerImplemented(t, err)
	require.Equal(t, "agt_target", request.Data.AgentID)
	created, err := handler.create(context.Background(), actionScopedJSON[roleCreateRequest]{
		Body: roleCreateRequest{RequestID: "req_create", Name: "Reviewer", Definition: "Review"},
	})
	requireRoleHandlerImplemented(t, err)
	require.Equal(t, "appr_1", created.Data.ApprovalID)
}

func TestRoleUpdateHandlerPreservesOmittedVersusExplicitEmptyDescription(t *testing.T) {
	fake := &fakeRoleRuntime{mutation: roleRequestResponse{ApprovalID: "appr_1", Status: "pending"}}
	handler := roleHandler{remote: fake}

	_, err := handler.update(context.Background(), actionScopedJSON[roleUpdateRequest]{
		Body: roleUpdateRequest{
			RequestID: "req_1", RoleID: "role_1",
			ExpectedRoleVersionID: "rlv_1", Name: roleString("Reviewer"),
		},
	})
	requireRoleHandlerImplemented(t, err)
	require.Nil(t, fake.updateInput.Description)

	_, err = handler.update(context.Background(), actionScopedJSON[roleUpdateRequest]{
		Body: roleUpdateRequest{
			RequestID: "req_2", RoleID: "role_1",
			ExpectedRoleVersionID: "rlv_1", Description: roleString(""),
		},
	})
	requireRoleHandlerImplemented(t, err)
	require.NotNil(t, fake.updateInput.Description)
	require.Empty(t, *fake.updateInput.Description)
}

type failingRoleRuntime struct{}

func (failingRoleRuntime) list(context.Context, roleListRequest) (roleListResult, error) {
	return roleListResult{}, &apiclient.APIError{StatusCode: http.StatusNotFound, Message: "upstream secret"}
}
func (failingRoleRuntime) get(context.Context, roleGetRequest) (roleGetResult, error) {
	panic("unused")
}
func (failingRoleRuntime) requestGet(context.Context, roleRequestGetRequest) (roleRequestGetResult, error) {
	panic("unused")
}
func (failingRoleRuntime) create(context.Context, roleCreateRequest) (roleRequestResponse, error) {
	panic("unused")
}
func (failingRoleRuntime) update(context.Context, roleUpdateRequest) (roleRequestResponse, error) {
	panic("unused")
}
func (failingRoleRuntime) delete(context.Context, roleDeleteRequest) (roleRequestResponse, error) {
	panic("unused")
}
func (failingRoleRuntime) apply(context.Context, roleApplyRequest) (roleRequestResponse, error) {
	panic("unused")
}

func TestRoleHandlerUsesStableSharedErrorClassifier(t *testing.T) {
	_, err := (roleHandler{remote: failingRoleRuntime{}}).list(
		context.Background(), actionScopedJSON[roleListRequest]{},
	)
	require.Error(t, err)
	var execError *Error
	require.ErrorAs(t, err, &execError)
	require.Equal(t, CodeNotFound, execError.Code)
	require.Equal(t, "list Roles failed", execError.Problem)
	require.NotContains(t, execError.Problem, "upstream secret")
}

func requireRoleHandlerImplemented(t *testing.T, err error) {
	t.Helper()
	require.NoError(t, err)
}

var _ roleRuntime = (*fakeRoleRuntime)(nil)
var _ roleRuntime = failingRoleRuntime{}
