package sfcrmappservice

import (
	"context"
	"encoding/json"
	"net/http"
	"net/http/httptest"
	"slices"
	"strings"
	"testing"

	catalogsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	auditsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/auditlogv1"
	permissionsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/effectivepermissionsv1"
)

func TestAdminAuditLogPreservesStableProjectionAndAuthority(t *testing.T) {
	oldValue, newValue := "old", "new"
	permissions := validPermissionResponse(17, 3)
	permissions.SystemPermissions = append(permissions.SystemPermissions,
		permissionsemantic.PermissionGrant{PermissionID: auditLogPermissionViewFieldAuditTrail, Granted: true})
	slices.SortFunc(permissions.SystemPermissions, func(left, right permissionsemantic.PermissionGrant) int {
		return strings.Compare(left.PermissionID, right.PermissionID)
	})
	var got auditsemantic.Request
	service := auditLogTestService(t, permissions, func(
		_ context.Context, _ string, request auditsemantic.Request,
	) (auditsemantic.Response, error) {
		got = request
		return auditsemantic.Response{
			Rows: []auditsemantic.Row{{
				ID: "audit-1", ObjectID: "Account", RecordID: "001", FieldID: "Name",
				OldValue: &oldValue, NewValue: &newValue, ChangedByID: "005",
				ChangedAt: "2026-08-14T12:00:00.000000123Z", Action: "UPDATE",
				Entrypoint: "REST", RequestID: "request-1", Outcome: "SUCCESS",
			}},
			Receipts: auditLogTestReceipts(17, 3),
		}, nil
	})
	request := httptest.NewRequest(http.MethodGet,
		"/services/data/v63.0/admin/audit/log?object=Account&recordId=001&limit=999&offset=7", nil)
	request.Header.Set("Authorization", "Bearer end-user")
	response := httptest.NewRecorder()
	service.ServeHTTP(response, request)
	if response.Code != http.StatusOK {
		t.Fatalf("status = %d, body = %s", response.Code, response.Body.String())
	}
	if got.ObjectID != "Account" || got.RecordID != "001" || got.Limit != auditsemantic.MaxRows ||
		got.Offset != 7 || got.MinCatalogRevision == nil || *got.MinCatalogRevision != 17 {
		t.Fatalf("Runtime request = %#v", got)
	}
	var body adminAuditLogResponse
	if err := json.Unmarshal(response.Body.Bytes(), &body); err != nil {
		t.Fatal(err)
	}
	if body.Count != 1 || body.Limit != auditsemantic.MaxRows || body.Offset != 7 || body.Object != "Account" ||
		body.Records[0].FieldName != "Name" || body.Records[0].OldValue == nil ||
		*body.Records[0].OldValue != "old" || body.Records[0].Entrypoint != "REST" ||
		body.Records[0].RequestID != "request-1" || body.Records[0].Outcome != "SUCCESS" {
		t.Fatalf("body = %#v", body)
	}
}

func TestAdminAuditLogDeniesBeforeAuditRuntime(t *testing.T) {
	permissions := validPermissionResponse(17, 3)
	calls := 0
	service := auditLogTestService(t, permissions, func(
		context.Context, string, auditsemantic.Request,
	) (auditsemantic.Response, error) {
		calls++
		return auditsemantic.Response{}, nil
	})
	request := httptest.NewRequest(http.MethodGet,
		"/services/data/v63.0/admin/audit/log", nil)
	request.Header.Set("Authorization", "Bearer end-user")
	response := httptest.NewRecorder()
	service.ServeHTTP(response, request)
	if response.Code != http.StatusForbidden || calls != 0 {
		t.Fatalf("status = %d, calls = %d, body = %s", response.Code, calls, response.Body.String())
	}
}

func TestAdminAuditLogValidatesParametersAfterAuthorization(t *testing.T) {
	permissions := validPermissionResponse(17, 3)
	permissions.SystemPermissions = append(permissions.SystemPermissions,
		permissionsemantic.PermissionGrant{PermissionID: auditLogPermissionViewFieldAuditTrail, Granted: true})
	slices.SortFunc(permissions.SystemPermissions, func(left, right permissionsemantic.PermissionGrant) int {
		return strings.Compare(left.PermissionID, right.PermissionID)
	})
	service := auditLogTestService(t, permissions, func(
		context.Context, string, auditsemantic.Request,
	) (auditsemantic.Response, error) {
		t.Fatal("audit Runtime called for an invalid request")
		return auditsemantic.Response{}, nil
	})
	request := httptest.NewRequest(http.MethodGet, "/services/data/v63.0/admin/audit/log", nil)
	request.Header.Set("Authorization", "Bearer end-user")
	response := httptest.NewRecorder()
	service.ServeHTTP(response, request)
	if response.Code != http.StatusBadRequest {
		t.Fatalf("status = %d, body = %s", response.Code, response.Body.String())
	}
}

func TestAdminAuditLogRejectsMixedRuntimeAuthority(t *testing.T) {
	permissions := validPermissionResponse(17, 3)
	permissions.SystemPermissions = append(permissions.SystemPermissions,
		permissionsemantic.PermissionGrant{PermissionID: auditLogPermissionViewFieldAuditTrail, Granted: true})
	slices.SortFunc(permissions.SystemPermissions, func(left, right permissionsemantic.PermissionGrant) int {
		return strings.Compare(left.PermissionID, right.PermissionID)
	})
	service := auditLogTestService(t, permissions, func(
		context.Context, string, auditsemantic.Request,
	) (auditsemantic.Response, error) {
		return auditsemantic.Response{Receipts: auditLogTestReceipts(18, 3)}, nil
	})
	request := httptest.NewRequest(http.MethodGet,
		"/services/data/v63.0/admin/audit/log?object=Account&recordId=001", nil)
	request.Header.Set("Authorization", "Bearer end-user")
	response := httptest.NewRecorder()
	service.ServeHTTP(response, request)
	if response.Code != http.StatusServiceUnavailable {
		t.Fatalf("status = %d, body = %s", response.Code, response.Body.String())
	}
}

func auditLogTestService(
	t *testing.T,
	permissions permissionsemantic.Response,
	readAudit RuntimeReadAuditLogFunc,
) *Service {
	t.Helper()
	config := permissionMetadataServiceConfig(func(
		_ context.Context, _ string, request permissionsemantic.Request,
	) (permissionsemantic.Response, error) {
		if request.TargetPrincipalID != "" || request.MinCatalogRevision != nil ||
			request.Selection == nil ||
			!slices.Equal(request.Selection.Sections, []permissionsemantic.Section{
				permissionsemantic.SectionSystemPermissions,
			}) || len(request.Selection.ResourceTypes) != 0 {
			t.Fatalf("effective-permission request = %#v", request)
		}
		return permissions, nil
	}, func(context.Context, catalogsemantic.GetRequest) (catalogsemantic.AppMetadataView, error) {
		return catalogsemantic.AppMetadataView{}, nil
	})
	config.RuntimeReadAuditLog = readAudit
	service, err := NewService(config)
	if err != nil {
		t.Fatal(err)
	}
	return service
}

func auditLogTestReceipts(revision, securityEpoch uint64) auditsemantic.Receipts {
	return auditsemantic.Receipts{
		ProtocolVersion: "1.0", CapabilityVersion: auditsemantic.CapabilityName,
		CatalogRevision: revision, SchemaGeneration: 8, EngineGeneration: 9,
		SecurityEpoch: securityEpoch,
	}
}
