package service

import (
	"context"
	"errors"
	"testing"

	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
	apiv1 "github.com/tidbcloud/cluster-service-ng/api/tidbcloud/cluster-agent/v1"

	agentcrypto "github.com/tidbcloud/cluster-service-ng/internal/cluster-agent/crypto"
	"github.com/tidbcloud/cluster-service-ng/internal/cluster-agent/internal/sqlexec"
	"github.com/tidbcloud/cluster-service-ng/internal/cluster-agent/internal/utils"
)

func TestAgentService_UpdateAuditLogSetting(t *testing.T) {
	t.Setenv("NAMESPACE", "tidb123")

	encryptor, err := agentcrypto.NewEncryptor()
	require.NoError(t, err)

	tests := []struct {
		name         string
		req          *apiv1.UpdateAuditLogSettingReq
		execErrs     []error
		wantErr      bool
		wantCmdNames []string
	}{
		{
			name: "disable audit log keeps required order",
			req: &apiv1.UpdateAuditLogSettingReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
				Enabled:           boolPtr(false),
				Unredacted:        boolPtr(true),
				Format:            "TEXT",
				MaxSizeMib:        10,
				MaxAgeSecond:      3600,
			},
			wantErr: false,
			wantCmdNames: []string{
				"SetAuditLogEnabled",
				"RotateAuditLog",
				"SetAuditLogFilePath",
				"SetAuditLogRedacted",
				"SetAuditLogFormat",
				"SetAuditLogMaxFilesize",
				"SetAuditLogMaxLifetime",
				"SetAuditLogReservedBackups",
			},
		},
		{
			name: "enable audit log appends enable command at the end",
			req: &apiv1.UpdateAuditLogSettingReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
				Enabled:           boolPtr(true),
				Unredacted:        boolPtr(false),
				Format:            "json",
				MaxSizeMib:        20,
				MaxAgeSecond:      600,
			},
			wantErr: false,
			wantCmdNames: []string{
				"SetAuditLogFilePath",
				"SetAuditLogRedacted",
				"SetAuditLogFormat",
				"SetAuditLogMaxFilesize",
				"SetAuditLogMaxLifetime",
				"SetAuditLogReservedBackups",
				"SetAuditLogEnabled",
			},
		},
		{
			name: "explicit false values are accepted",
			req: &apiv1.UpdateAuditLogSettingReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
				Enabled:           boolPtr(false),
				Unredacted:        boolPtr(false),
				Format:            "TEXT",
				MaxSizeMib:        10,
				MaxAgeSecond:      3600,
			},
			wantErr: false,
			wantCmdNames: []string{
				"SetAuditLogEnabled",
				"RotateAuditLog",
				"SetAuditLogFilePath",
				"SetAuditLogRedacted",
				"SetAuditLogFormat",
				"SetAuditLogMaxFilesize",
				"SetAuditLogMaxLifetime",
				"SetAuditLogReservedBackups",
			},
		},
		{
			name: "missing enabled",
			req: &apiv1.UpdateAuditLogSettingReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
				Unredacted:        boolPtr(true),
				Format:            "TEXT",
				MaxSizeMib:        20,
				MaxAgeSecond:      600,
			},
			wantErr: true,
		},
		{
			name: "missing unredacted",
			req: &apiv1.UpdateAuditLogSettingReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
				Enabled:           boolPtr(true),
				Format:            "TEXT",
				MaxSizeMib:        20,
				MaxAgeSecond:      600,
			},
			wantErr: true,
		},
		{
			name: "executor failed",
			req: &apiv1.UpdateAuditLogSettingReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
				Enabled:           boolPtr(true),
				Unredacted:        boolPtr(false),
				Format:            "TEXT",
				MaxSizeMib:        20,
				MaxAgeSecond:      600,
			},
			execErrs: []error{errors.New("exec failed")},
			wantErr:  true,
		},
		{
			name: "invalid format",
			req: &apiv1.UpdateAuditLogSettingReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
				Enabled:           boolPtr(true),
				Unredacted:        boolPtr(false),
				Format:            "yaml",
				MaxSizeMib:        20,
				MaxAgeSecond:      600,
			},
			wantErr: true,
		},
		{
			name:    "nil request",
			req:     nil,
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			stubExec := &stubAuditLogExecutor{errs: tt.execErrs}

			oldFactory := newAuditLogExecutor
			newAuditLogExecutor = func(
				resolver utils.AddressResolver,
				auth sqlexec.DBAuth,
				tlsProvider sqlexec.TLSConfigProvider,
			) auditLogExecutor {
				require.NotNil(t, resolver)
				require.NotNil(t, tlsProvider)
				if tt.req != nil {
					assert.Equal(t, "cloud_admin", auth.Username)
					assert.Equal(t, "secret", auth.Password.PlainText())
				}
				return stubExec
			}
			t.Cleanup(func() {
				newAuditLogExecutor = oldFactory
			})

			svc := &AgentService{
				encryptor: encryptor,
			}

			_, err := svc.UpdateAuditLogSetting(context.Background(), tt.req)
			if tt.wantErr {
				require.Error(t, err)
				return
			}
			require.NoError(t, err)
			require.Len(t, stubExec.calls, 1)
			assert.Equal(t, "cluster-1", stubExec.calls[0].clusterID)
			assert.Equal(t, tt.wantCmdNames, stubExec.calls[0].cmdNames)
		})
	}
}

func boolPtr(v bool) *bool {
	return &v
}
