package api

import (
	"context"
	"testing"

	"connectrpc.com/connect"
	"github.com/google/uuid"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"

	apiv1 "github.com/OliveTin/OliveTin/gen/olivetin/api/v1"
	"github.com/OliveTin/OliveTin/internal/auth"
	config "github.com/OliveTin/OliveTin/internal/config"
	"github.com/OliveTin/OliveTin/internal/executor"
)

func TestStartActionRequiresJustificationForGuest(t *testing.T) {
	cfg := config.DefaultConfig()
	action := &config.Action{
		Title:         "Send email",
		ID:            "send_email",
		Justification: config.JustificationRequiredNoTemplate,
		Shell:         "echo done",
	}
	cfg.Actions = append(cfg.Actions, action)

	ex := executor.DefaultExecutor(cfg)
	ex.RebuildActionMap()
	binding := ex.FindBindingWithNoEntity(action)
	require.NotNil(t, binding)

	ts, client := getNewTestServerAndClientWithExecutor(cfg, ex)
	defer ts.Close()

	_, err := client.StartAction(context.Background(), connect.NewRequest(&apiv1.StartActionRequest{
		BindingId:        binding.ID,
		UniqueTrackingId: uuid.NewString(),
	}))
	require.Error(t, err)
	assert.Equal(t, connect.CodeInvalidArgument, connect.CodeOf(err))

	resp, err := client.StartAction(context.Background(), connect.NewRequest(&apiv1.StartActionRequest{
		BindingId:        binding.ID,
		UniqueTrackingId: uuid.NewString(),
		Justification:    "New user registration foo@example.com",
	}))
	require.NoError(t, err)
	require.NotEmpty(t, resp.Msg.ExecutionTrackingId)

	waitForLogJustification(t, ex, resp.Msg.ExecutionTrackingId, "New user registration foo@example.com")
}

func TestBuildActionExposesJustificationTemplate(t *testing.T) {
	cfg := config.DefaultConfig()
	action := &config.Action{
		Title:         "Audited action",
		ID:            "audited",
		Justification: "{{ target }}",
		Shell:         "echo hi",
	}
	cfg.Actions = append(cfg.Actions, action)

	ex := executor.DefaultExecutor(cfg)
	ex.RebuildActionMap()
	binding := ex.FindBindingWithNoEntity(action)
	require.NotNil(t, binding)

	pb := buildAction(binding, &DashboardRenderRequest{
		cfg: cfg,
		ex:  ex,
	})

	require.NotNil(t, pb)
	assert.Equal(t, "{{ target }}", pb.Justification)
}

func TestBuildActionExposesBlankRequiredJustification(t *testing.T) {
	cfg := config.DefaultConfig()
	action := &config.Action{
		Title:         "Audited action",
		ID:            "audited",
		Justification: config.JustificationRequiredNoTemplate,
		Shell:         "echo hi",
	}
	cfg.Actions = append(cfg.Actions, action)

	ex := executor.DefaultExecutor(cfg)
	ex.RebuildActionMap()
	binding := ex.FindBindingWithNoEntity(action)
	require.NotNil(t, binding)

	pb := buildAction(binding, &DashboardRenderRequest{
		cfg: cfg,
		ex:  ex,
	})

	require.NotNil(t, pb)
	assert.Equal(t, config.JustificationRequiredNoTemplate, pb.Justification)
}

func TestResolveStartJustificationUsesTemplateWhenClientValueEmpty(t *testing.T) {
	action := &config.Action{
		Justification: "{{ ansible_host }}",
	}
	binding := &executor.ActionBinding{}

	got := resolveStartJustification(action, binding, "", map[string]string{
		"ansible_host": "192.168.66.8",
	})
	assert.Equal(t, "192.168.66.8", got)
}

func TestResolveStartJustificationPrefersClientValue(t *testing.T) {
	action := &config.Action{
		Justification: "{{ ansible_host }}",
	}
	binding := &executor.ActionBinding{}

	got := resolveStartJustification(action, binding, "manual reason", map[string]string{
		"ansible_host": "192.168.66.8",
	})
	assert.Equal(t, "manual reason", got)
}

func TestStartActionResolvesJustificationTemplateForGuest(t *testing.T) {
	cfg := config.DefaultConfig()
	action := &config.Action{
		Title:         "Run playbook",
		ID:            "run_playbook",
		Justification: "{{ ansible_host }}",
		Shell:         "echo done",
		Arguments: []config.ActionArgument{
			{Name: "ansible_host", Title: "Host"},
		},
	}
	cfg.Actions = append(cfg.Actions, action)

	ex := executor.DefaultExecutor(cfg)
	ex.RebuildActionMap()
	binding := ex.FindBindingWithNoEntity(action)
	require.NotNil(t, binding)

	ts, client := getNewTestServerAndClientWithExecutor(cfg, ex)
	defer ts.Close()

	resp, err := client.StartAction(context.Background(), connect.NewRequest(&apiv1.StartActionRequest{
		BindingId:        binding.ID,
		UniqueTrackingId: uuid.NewString(),
		Arguments: []*apiv1.StartActionArgument{
			{Name: "ansible_host", Value: "stuffbox"},
		},
	}))
	require.NoError(t, err)
	require.NotEmpty(t, resp.Msg.ExecutionTrackingId)

	waitForLogJustification(t, ex, resp.Msg.ExecutionTrackingId, "stuffbox")
}

func TestValidateJustificationRequiredAllowsSystemUser(t *testing.T) {
	cfg := config.DefaultConfig()
	action := &config.Action{Title: "Cron job", Justification: config.JustificationRequiredNoTemplate}

	err := validateJustificationRequired(action, "", auth.UserFromSystem(cfg, "cron"))
	require.NoError(t, err)
}
