package runtimeapi

import (
	"context"
	"testing"
	"time"

	"github.com/nexuscrm/shared/pkg/constants"
	sharedcontract "github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	auditsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/auditlogv1"
	"github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/permissionstatev1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
	"google.golang.org/grpc/codes"
	"google.golang.org/grpc/status"
)

type auditLogTestReader struct {
	calls   int
	request sharedcontract.AuditLogQueryRequest
	result  *sharedcontract.AuditLogQueryResult
	err     error
}

func (reader *auditLogTestReader) QueryAuditLog(
	_ context.Context,
	request sharedcontract.AuditLogQueryRequest,
	_ *models.UserSession,
) (*sharedcontract.AuditLogQueryResult, error) {
	reader.calls++
	reader.request = request
	return reader.result, reader.err
}

type auditLogTestPermissions struct{ grants map[string]bool }

func (engine *auditLogTestPermissions) EffectivePermissions(
	context.Context,
	*models.UserSession,
	string,
	permissionstatev1.Version,
) (*sharedcontract.EffectivePermissionsResult, error) {
	return &sharedcontract.EffectivePermissionsResult{
		SystemPermissions: engine.grants,
	}, nil
}

func TestAuditLogReadRequiresPermissionAndReturnsCompleteRow(t *testing.T) {
	oldValue, newValue := "old", "new"
	action, outcome := "UPDATE", "SUCCESS"
	changedAt := time.Date(2026, 8, 14, 12, 0, 0, 123, time.UTC)
	reader := &auditLogTestReader{result: &sharedcontract.AuditLogQueryResult{
		Object: "Account", Limit: 100, Offset: 3,
		Rows: []sharedcontract.AuditLogRow{{
			ID: "audit-1", ObjectAPIName: "Account", RecordID: "001",
			FieldName: "Name", OldValue: &oldValue, NewValue: &newValue,
			ChangedByID: "005", ChangedAt: changedAt, ChangedAtValid: true,
			Action: &action, Outcome: &outcome,
		}},
	}}
	permissions := &auditLogTestPermissions{grants: map[string]bool{
		string(constants.PermViewFieldAuditTrail): true,
	}}
	server, err := NewAuditLogReadServer(reader, permissions)
	if err != nil {
		t.Fatal(err)
	}
	ctx := auditLogTestContext(t)
	semanticRequest := auditsemantic.Request{
		ObjectID: "Account", RecordID: "001", Limit: 100, Offset: 3,
	}
	request, err := mapping.ToWireReadAuditLogRequest(ctx, semanticRequest)
	if err != nil {
		t.Fatal(err)
	}
	wireResponse, err := server.ReadAuditLog(ctx, request)
	if err != nil {
		t.Fatal(err)
	}
	response, err := mapping.FromWireReadAuditLogResponse(semanticRequest, wireResponse)
	if err != nil {
		t.Fatal(err)
	}
	if reader.calls != 1 || reader.request.ObjectAPIName != "Account" ||
		reader.request.RecordID != "001" || reader.request.Limit != 100 || reader.request.Offset != 3 {
		t.Fatalf("reader call = %d, request = %#v", reader.calls, reader.request)
	}
	if len(response.Rows) != 1 || response.Rows[0].ChangedAt != changedAt.Format(time.RFC3339Nano) ||
		response.Rows[0].Action != action || response.Rows[0].Outcome != outcome ||
		response.Rows[0].OldValue == nil || *response.Rows[0].OldValue != oldValue {
		t.Fatalf("response = %#v", response)
	}
}

func TestAuditLogReadDeniesBeforeStorage(t *testing.T) {
	reader := &auditLogTestReader{}
	server, err := NewAuditLogReadServer(reader, &auditLogTestPermissions{grants: map[string]bool{}})
	if err != nil {
		t.Fatal(err)
	}
	ctx := auditLogTestContext(t)
	request, err := mapping.ToWireReadAuditLogRequest(ctx, auditsemantic.Request{
		ObjectID: "Account", RecordID: "001", Limit: 100,
	})
	if err != nil {
		t.Fatal(err)
	}
	if _, err := server.ReadAuditLog(ctx, request); status.Code(err) != codes.PermissionDenied {
		t.Fatalf("status = %v, want PermissionDenied", err)
	}
	if reader.calls != 0 {
		t.Fatalf("reader calls = %d, want zero", reader.calls)
	}
}

func auditLogTestContext(t *testing.T) context.Context {
	t.Helper()
	ctx := reportReadTestContext(t)
	scope, ok := runtimeExecutionScopeFromContext(ctx)
	if !ok {
		t.Fatal("missing Runtime execution scope")
	}
	scope.capability = auditsemantic.CapabilityName
	scope.receipts = receiptsForCapability(scope.receipts, auditsemantic.CapabilityName)
	return context.WithValue(ctx, runtimeExecutionScopeContextKey{}, scope)
}
