package serviceactor

import (
	"context"
	"database/sql"
	"encoding/json"
	"errors"
	"reflect"
	"strings"
	"testing"
	"time"

	"github.com/tidbcloud/AgentTeam/internal/coredb"
)

func TestSyncGrantScopesBatchesRevokesAndInserts(t *testing.T) {
	t.Parallel()

	now := time.Date(2026, 6, 7, 17, 30, 0, 0, time.UTC)
	execer := &scriptedGrantSyncExecer{
		activeScopes: []string{" profile.read ", "channels.read"},
	}
	installation := Installation{
		ID:                  " svcinst-1 ",
		WorkspaceID:         " wsp-1 ",
		ServiceDefinitionID: " svcdef-1 ",
		ServiceKind:         " mcp ",
	}

	err := syncGrantScopes(context.Background(), execer, installation, " mem-1 ", []string{"tasks.write", "profile.read", "messages.write"}, now)
	if err != nil {
		t.Fatalf("syncGrantScopes returned error: %v", err)
	}
	if got, want := execer.queryArgs, []any{"svcinst-1"}; !reflect.DeepEqual(got, want) {
		t.Fatalf("query args = %#v, want %#v", got, want)
	}
	if got, want := len(execer.execCalls), 2; got != want {
		t.Fatalf("exec call count = %d, want %d", got, want)
	}

	revoke := execer.execCalls[0]
	if !strings.Contains(revoke.query, "scope IN ($3)") {
		t.Fatalf("revoke query was not batched:\n%s", revoke.query)
	}
	if got, want := revoke.args, []any{"svcinst-1", now, "channels.read"}; !reflect.DeepEqual(got, want) {
		t.Fatalf("revoke args = %#v, want %#v", got, want)
	}

	insert := execer.execCalls[1]
	if !strings.Contains(insert.query, "VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14),") ||
		!strings.Contains(insert.query, "($15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28)") {
		t.Fatalf("insert query was not a multi-row insert:\n%s", insert.query)
	}
	if got, want := len(insert.args), 28; got != want {
		t.Fatalf("insert arg count = %d, want %d", got, want)
	}
	if got, want := insert.args[1], any("wsp-1"); got != want {
		t.Fatalf("first insert workspace arg = %#v, want %#v", got, want)
	}
	if got, want := insert.args[2], any("svcdef-1"); got != want {
		t.Fatalf("first insert definition arg = %#v, want %#v", got, want)
	}
	if got, want := insert.args[3], any("svcinst-1"); got != want {
		t.Fatalf("first insert installation arg = %#v, want %#v", got, want)
	}
	if got, want := insert.args[4], any("mcp"); got != want {
		t.Fatalf("first insert service kind arg = %#v, want %#v", got, want)
	}
	if got, want := insert.args[5], any("messages.write"); got != want {
		t.Fatalf("first insert scope arg = %#v, want %#v", got, want)
	}
	if got, want := string(insert.args[6].([]byte)), `{}`; got != want {
		t.Fatalf("first insert config arg = %#v, want %#v", got, want)
	}
	if got, want := insert.args[7], any(int64(1)); got != want {
		t.Fatalf("first insert policy version arg = %#v, want %#v", got, want)
	}
	if got, want := insert.args[9], any("mem-1"); got != want {
		t.Fatalf("first insert member arg = %#v, want %#v", got, want)
	}
	if got, want := insert.args[15], any("wsp-1"); got != want {
		t.Fatalf("second insert workspace arg = %#v, want %#v", got, want)
	}
	if got, want := insert.args[19], any("tasks.write"); got != want {
		t.Fatalf("second insert scope arg = %#v, want %#v", got, want)
	}
	if got, want := insert.args[23], any("mem-1"); got != want {
		t.Fatalf("second insert member arg = %#v, want %#v", got, want)
	}
}

func TestListActiveGrantScopesExcludesExpiredPolicies(t *testing.T) {
	t.Parallel()

	execer := &scriptedGrantSyncExecer{activeScopes: []string{"messages.write"}}
	scopes, err := listActiveGrantScopes(context.Background(), execer, "svcinst-1")
	if err != nil {
		t.Fatalf("listActiveGrantScopes returned error: %v", err)
	}
	if got, want := scopes, []string{"messages.write"}; !reflect.DeepEqual(got, want) {
		t.Fatalf("scopes = %#v, want %#v", got, want)
	}
	if !strings.Contains(execer.query, "expires_at IS NULL OR expires_at > CURRENT_TIMESTAMP") {
		t.Fatalf("active grant query does not exclude expired grants:\n%s", execer.query)
	}
}

func TestSyncGrantScopesRestoresRevokedGrantWithoutDroppingPolicy(t *testing.T) {
	t.Parallel()

	now := time.Date(2026, 6, 7, 18, 0, 0, 0, time.UTC)
	execer := &scriptedGrantSyncExecer{restoreGrantIDs: map[string]string{"app.notifications.write": "svcgr-policy"}}
	err := syncGrantScopes(context.Background(), execer, Installation{
		ID: "svcinst-1", WorkspaceID: "wsp-1", ServiceDefinitionID: "svcdef-1", ServiceKind: "app",
	}, "mem-1", []string{"app.notifications.write"}, now)
	if err != nil {
		t.Fatalf("syncGrantScopes returned error: %v", err)
	}
	if got, want := len(execer.execCalls), 1; got != want {
		t.Fatalf("exec call count = %d, want %d", got, want)
	}
	restore := execer.execCalls[0]
	if !strings.Contains(restore.query, "revoked_at = NULL") || strings.Contains(restore.query, "config =") || strings.Contains(restore.query, "policy_version =") {
		t.Fatalf("restore query must preserve policy fields:\n%s", restore.query)
	}
	if got, want := restore.args, []any{"mem-1", now, "svcgr-policy", "svcinst-1", "app.notifications.write"}; !reflect.DeepEqual(got, want) {
		t.Fatalf("restore args = %#v, want %#v", got, want)
	}
}

func TestSetGrantPolicyUsesVersionFenceAndObject(t *testing.T) {
	t.Parallel()

	now := time.Date(2026, 6, 7, 19, 0, 0, 0, time.UTC)
	expiresAt := now.Add(24 * time.Hour)
	execer := &scriptedGrantPolicyExecer{grant: Grant{
		ID: "svcgr-1", WorkspaceID: "wsp-1", ServiceDefinitionID: "svcdef-1",
		ServiceInstallationID: "svcinst-1", ServiceKind: "app", Scope: "app.notifications.write",
		Config: json.RawMessage(`{}`), PolicyVersion: 1, GrantedByMemberID: "mem-1",
		GrantedAt: now.Add(-time.Hour), CreatedAt: now.Add(-time.Hour), UpdatedAt: now.Add(-time.Hour),
	}}
	digest := strings.Repeat("a", 64)
	updated, err := (&TxStore{db: execer}).SetGrantPolicy(
		context.Background(), "svcinst-1", "app.notifications.write", 1,
		json.RawMessage(`{"allowed":["approval_assignee"]}`), digest, &expiresAt, now,
	)
	if err != nil {
		t.Fatalf("SetGrantPolicy returned error: %v", err)
	}
	if got, want := updated.PolicyVersion, int64(2); got != want {
		t.Fatalf("policy version = %d, want %d", got, want)
	}
	if got, want := updated.PolicyDigest, digest; got != want {
		t.Fatalf("policy digest = %q, want %q", got, want)
	}
	if got, want := len(execer.execCalls), 1; got != want {
		t.Fatalf("exec calls = %d, want %d", got, want)
	}
	call := execer.execCalls[0]
	if !strings.Contains(call.query, "policy_version = $2") || !strings.Contains(call.query, "AND policy_version = $9") || !strings.Contains(call.query, "AND revoked_at IS NULL") {
		t.Fatalf("policy update lacks version/active fences:\n%s", call.query)
	}
	if got, want := call.args[1], any(int64(2)); got != want {
		t.Fatalf("next policy version = %#v, want %#v", got, want)
	}

	_, err = (&TxStore{db: execer}).SetGrantPolicy(
		context.Background(), "svcinst-1", "app.notifications.write", 2,
		json.RawMessage(`{}`), digest, nil, now,
	)
	if !errors.Is(err, ErrConflict) {
		t.Fatalf("stale current record error = %v, want conflict", err)
	}
}

type scriptedGrantSyncExecer struct {
	activeScopes    []string
	restoreGrantIDs map[string]string
	query           string
	queryArgs       []any
	execCalls       []scriptedServiceGrantExecCall
}

func (execer *scriptedGrantSyncExecer) QueryRow(_ context.Context, _ string, args ...any) coredb.Row {
	if len(args) >= 2 {
		if id := execer.restoreGrantIDs[args[1].(string)]; id != "" {
			return scriptedServiceGrantRow{value: id}
		}
	}
	return scriptedServiceGrantRow{err: sql.ErrNoRows}
}

func (execer *scriptedGrantSyncExecer) Query(_ context.Context, query string, args ...any) (coredb.Rows, error) {
	execer.query = query
	execer.queryArgs = append([]any(nil), args...)
	return &scriptedActiveServiceGrantRows{scopes: execer.activeScopes}, nil
}

func (execer *scriptedGrantSyncExecer) Exec(_ context.Context, query string, args ...any) (coredb.Result, error) {
	execer.execCalls = append(execer.execCalls, scriptedServiceGrantExecCall{
		query: query,
		args:  append([]any(nil), args...),
	})
	return scriptedServiceGrantResult(1), nil
}

type scriptedServiceGrantExecCall struct {
	query string
	args  []any
}

type scriptedServiceGrantResult int64

func (result scriptedServiceGrantResult) RowsAffected() int64 {
	return int64(result)
}

type scriptedServiceGrantRow struct {
	err   error
	value string
}

func (row scriptedServiceGrantRow) Scan(dest ...any) error {
	if row.err == nil && len(dest) == 1 {
		*(dest[0].(*string)) = row.value
	}
	return row.err
}

type scriptedGrantPolicyExecer struct {
	grant     Grant
	execCalls []scriptedServiceGrantExecCall
}

func (execer *scriptedGrantPolicyExecer) QueryRow(context.Context, string, ...any) coredb.Row {
	return scriptedGrantPolicyRow{grant: execer.grant}
}

func (execer *scriptedGrantPolicyExecer) Query(context.Context, string, ...any) (coredb.Rows, error) {
	return &scriptedActiveServiceGrantRows{}, nil
}

func (execer *scriptedGrantPolicyExecer) Exec(_ context.Context, query string, args ...any) (coredb.Result, error) {
	execer.execCalls = append(execer.execCalls, scriptedServiceGrantExecCall{query: query, args: append([]any(nil), args...)})
	return scriptedServiceGrantResult(1), nil
}

type scriptedGrantPolicyRow struct{ grant Grant }

func (row scriptedGrantPolicyRow) Scan(dest ...any) error {
	grant := row.grant
	*(dest[0].(*string)) = grant.ID
	*(dest[1].(*string)) = grant.WorkspaceID
	*(dest[2].(*string)) = grant.ServiceDefinitionID
	*(dest[3].(*string)) = grant.ServiceInstallationID
	*(dest[4].(*string)) = grant.ServiceKind
	*(dest[5].(*string)) = grant.Scope
	*(dest[6].(*[]byte)) = append([]byte(nil), grant.Config...)
	*(dest[7].(*int64)) = grant.PolicyVersion
	*(dest[8].(*string)) = grant.PolicyDigest
	*(dest[9].(*string)) = grant.GrantedByMemberID
	*(dest[10].(*time.Time)) = grant.GrantedAt
	if grant.ExpiresAt != nil {
		*(dest[11].(*sql.NullTime)) = sql.NullTime{Time: *grant.ExpiresAt, Valid: true}
	}
	if grant.RevokedAt != nil {
		*(dest[12].(*sql.NullTime)) = sql.NullTime{Time: *grant.RevokedAt, Valid: true}
	}
	*(dest[13].(*time.Time)) = grant.CreatedAt
	*(dest[14].(*time.Time)) = grant.UpdatedAt
	return nil
}

type scriptedActiveServiceGrantRows struct {
	scopes []string
	idx    int
}

func (rows *scriptedActiveServiceGrantRows) Close()     {}
func (rows *scriptedActiveServiceGrantRows) Err() error { return nil }
func (rows *scriptedActiveServiceGrantRows) Next() bool { return rows.idx < len(rows.scopes) }
func (rows *scriptedActiveServiceGrantRows) Scan(dest ...any) error {
	scope := rows.scopes[rows.idx]
	rows.idx++
	*(dest[0].(*string)) = scope
	return nil
}
