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"
)

type stubLogRedactionQueryExecutor struct {
	result   sqlexec.QueryResult
	err      error
	calls    int
	lastID   string
	lastName string
}

func (e *stubLogRedactionQueryExecutor) Query(
	_ context.Context,
	clusterID string,
	cmd sqlexec.QueryCommand,
) (sqlexec.QueryResult, error) {
	e.calls++
	e.lastID = clusterID
	if cmd != nil {
		e.lastName = cmd.Name()
	}
	if e.err != nil {
		return sqlexec.QueryResult{}, e.err
	}
	return e.result, nil
}

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

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

	tests := []struct {
		name          string
		req           *apiv1.UpdateLogRedactionPolicyReq
		execErrs      []error
		wantErr       bool
		wantExecCall  bool
		wantClusterID string
		wantCmdNames  []string
	}{
		{
			name:    "nil request",
			req:     nil,
			wantErr: true,
		},
		{
			name: "nil redact_log",
			req: &apiv1.UpdateLogRedactionPolicyReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
				RedactLog:         nil,
			},
			wantErr: true,
		},
		{
			name: "empty cluster_id",
			req: &apiv1.UpdateLogRedactionPolicyReq{
				ClusterId:         "",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
				RedactLog:         boolPtr(true),
			},
			wantErr: true,
		},
		{
			name: "empty encrypted_username",
			req: &apiv1.UpdateLogRedactionPolicyReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: nil,
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
				RedactLog:         boolPtr(true),
			},
			wantErr: true,
		},
		{
			name: "empty encrypted_password",
			req: &apiv1.UpdateLogRedactionPolicyReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: nil,
				RedactLog:         boolPtr(true),
			},
			wantErr: true,
		},
		{
			name: "executor failed",
			req: &apiv1.UpdateLogRedactionPolicyReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
				RedactLog:         boolPtr(true),
			},
			execErrs:      []error{errors.New("exec failed")},
			wantErr:       true,
			wantExecCall:  true,
			wantClusterID: "cluster-1",
			wantCmdNames:  []string{"SetLogRedactionPolicy"},
		},
		{
			name: "success redact on",
			req: &apiv1.UpdateLogRedactionPolicyReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
				RedactLog:         boolPtr(true),
			},
			wantExecCall:  true,
			wantClusterID: "cluster-1",
			wantCmdNames:  []string{"SetLogRedactionPolicy"},
		},
		{
			name: "success redact off",
			req: &apiv1.UpdateLogRedactionPolicyReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
				RedactLog:         boolPtr(false),
			},
			wantExecCall:  true,
			wantClusterID: "cluster-1",
			wantCmdNames:  []string{"SetLogRedactionPolicy"},
		},
	}

	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)
				return stubExec
			}
			t.Cleanup(func() {
				newAuditLogExecutor = oldFactory
			})

			svc := &AgentService{encryptor: encryptor}
			resp, err := svc.UpdateLogRedactionPolicy(context.Background(), tt.req)
			if tt.wantErr {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
				require.NotNil(t, resp)
				assert.Equal(t, "success", resp.Message)
			}

			if tt.wantExecCall {
				require.Len(t, stubExec.calls, 1)
				assert.Equal(t, tt.wantClusterID, stubExec.calls[0].clusterID)
				assert.Equal(t, tt.wantCmdNames, stubExec.calls[0].cmdNames)
				return
			}
			assert.Empty(t, stubExec.calls)
		})
	}
}

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

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

	tests := []struct {
		name          string
		req           *apiv1.GetLogRedactionPolicyReq
		queryResult   sqlexec.QueryResult
		queryErr      error
		wantErr       bool
		wantExecCall  bool
		wantClusterID string
		wantRedact    bool
	}{
		{
			name:    "nil request",
			req:     nil,
			wantErr: true,
		},
		{
			name: "empty cluster_id",
			req: &apiv1.GetLogRedactionPolicyReq{
				ClusterId:         "",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
			},
			wantErr: true,
		},
		{
			name: "empty encrypted_username",
			req: &apiv1.GetLogRedactionPolicyReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: nil,
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
			},
			wantErr: true,
		},
		{
			name: "empty encrypted_password",
			req: &apiv1.GetLogRedactionPolicyReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: nil,
			},
			wantErr: true,
		},
		{
			name: "query failed",
			req: &apiv1.GetLogRedactionPolicyReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
			},
			queryErr:      errors.New("query failed"),
			wantErr:       true,
			wantExecCall:  true,
			wantClusterID: "cluster-1",
		},
		{
			name: "parse error: empty rows",
			req: &apiv1.GetLogRedactionPolicyReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
			},
			queryResult: sqlexec.QueryResult{
				Columns: []string{"Variable_name", "Value"},
				Rows:    nil,
			},
			wantErr:       true,
			wantExecCall:  true,
			wantClusterID: "cluster-1",
		},
		{
			name: "success redact on",
			req: &apiv1.GetLogRedactionPolicyReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
			},
			queryResult: sqlexec.QueryResult{
				Columns: []string{"Variable_name", "Value"},
				Rows:    [][]any{{[]byte("tidb_redact_log"), []byte("ON")}},
			},
			wantExecCall:  true,
			wantClusterID: "cluster-1",
			wantRedact:    true,
		},
		{
			name: "success redact off",
			req: &apiv1.GetLogRedactionPolicyReq{
				ClusterId:         "cluster-1",
				EncryptedUsername: encryptForTest(t, encryptor, "cloud_admin"),
				EncryptedPassword: encryptForTest(t, encryptor, "secret"),
			},
			queryResult: sqlexec.QueryResult{
				Columns: []string{"Variable_name", "Value"},
				Rows:    [][]any{{[]byte("tidb_redact_log"), []byte("OFF")}},
			},
			wantExecCall:  true,
			wantClusterID: "cluster-1",
			wantRedact:    false,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			stubExec := &stubLogRedactionQueryExecutor{
				result: tt.queryResult,
				err:    tt.queryErr,
			}

			oldFactory := newLogRedactionQueryExecutor
			newLogRedactionQueryExecutor = func(
				resolver utils.AddressResolver,
				auth sqlexec.DBAuth,
				tlsProvider sqlexec.TLSConfigProvider,
			) logRedactionQueryExecutor {
				require.NotNil(t, resolver)
				require.NotNil(t, tlsProvider)
				return stubExec
			}
			t.Cleanup(func() {
				newLogRedactionQueryExecutor = oldFactory
			})

			svc := &AgentService{encryptor: encryptor}
			resp, err := svc.GetLogRedactionPolicy(context.Background(), tt.req)
			if tt.wantErr {
				require.Error(t, err)
			} else {
				require.NoError(t, err)
				require.NotNil(t, resp)
				assert.Equal(t, tt.wantRedact, resp.RedactLog)
			}

			if tt.wantExecCall {
				assert.Equal(t, 1, stubExec.calls)
				assert.Equal(t, tt.wantClusterID, stubExec.lastID)
				assert.Equal(t, "GetLogRedactionPolicy", stubExec.lastName)
				return
			}
			assert.Equal(t, 0, stubExec.calls)
		})
	}
}
