package dialectsfcrm_test

import (
	"reflect"
	"testing"

	dialectsfcrm "github.com/nexuscrm/dialect-sf-crm"
	"github.com/nexuscrm/dialect-sf-crm/wire"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/dialect"
	sharederrors "github.com/nexuscrm/shared/pkg/errors"
)

// TestV63CompositeTreeErrorProjectorBinderSeamPaths pins the exact (status, body)
// the module projector returns for the two binder-injected transport-level wire
// errors (binder.go:376-386): a body-read failure (WireMalformedRequest -> 400) and
// an oversize body (WireRequestTooLarge -> 413). These are the strings the BACKEND
// binder emits verbatim (binder.go:378-385), so this is the no-binder unit pin that
// guards the binder-seam wire shape from the module side.
func TestV63CompositeTreeErrorProjectorBinderSeamPaths(t *testing.T) {
	t.Run("malformed request -> 400", func(t *testing.T) {
		status, body, projected := dialectsfcrm.V63CompositeTreeErrorProjector(
			&dialect.Request{Version: "v63.0"},
			sharederrors.NewMalformedRequestError("Invalid request body"),
		)
		if !projected {
			t.Fatal("projector declined a WireMalformedRequest; must project")
		}
		if status != 400 {
			t.Fatalf("status = %d, want 400", status)
		}
		want := []wire.SFError{{ErrorCode: "INVALID_REQUEST", Message: "Invalid request body"}}
		if !reflect.DeepEqual(body, want) {
			t.Fatalf("body = %#v, want %#v", body, want)
		}
	})

	t.Run("request too large -> 413", func(t *testing.T) {
		status, body, projected := dialectsfcrm.V63CompositeTreeErrorProjector(
			&dialect.Request{Version: "v63.0"},
			sharederrors.NewRequestTooLargeError("Request body exceeds the maximum allowed size of 25 MB"),
		)
		if !projected {
			t.Fatal("projector declined a WireRequestTooLarge; must project")
		}
		if status != 413 {
			t.Fatalf("status = %d, want 413", status)
		}
		want := []wire.SFError{{ErrorCode: "INVALID_REQUEST", Message: "Request body exceeds the maximum allowed size of 25 MB"}}
		if !reflect.DeepEqual(body, want) {
			t.Fatalf("body = %#v, want %#v", body, want)
		}
	})
}

func TestV63InternalErrorProjector(t *testing.T) {
	status, body := dialectsfcrm.V63InternalErrorProjector(&dialect.Request{Version: "v63.0"})
	if status != 500 {
		t.Fatalf("status = %d, want 500", status)
	}
	if want := wire.SFGenericInternalErrorResponse(); !reflect.DeepEqual(body, want) {
		t.Fatalf("body = %#v, want %#v", body, want)
	}
}

// TestCompositeTreePartitionIdentity pins the partition's declared identity (the v63
// tuple reproduced as the CRM pack's own L1 facts). While the transitional SDK shell
// exists, equality vs the SDK Dialect is pinned by backend's door seal tests.
func TestCompositeTreePartitionIdentity(t *testing.T) {
	p := dialectsfcrm.NewCompositeTreePartition()
	if p.Name() != "salesforce-v63" {
		t.Fatalf("Name = %q", p.Name())
	}
	if p.OwningPack() != "salesforce-crm-standard" {
		t.Fatalf("OwningPack = %q", p.OwningPack())
	}
	if p.BasePath() != "/services/data" {
		t.Fatalf("BasePath = %q", p.BasePath())
	}
	if want := []string{"v59.0", "v60.0", "v61.0", "v62.0", "v63.0"}; !reflect.DeepEqual(p.Versions(), want) {
		t.Fatalf("Versions = %v, want %v", p.Versions(), want)
	}
	if want := map[string]string{
		"v59.0": "Winter '24",
		"v60.0": "Spring '24",
		"v61.0": "Summer '24",
		"v62.0": "Winter '25",
		"v63.0": "Spring '25",
	}; !reflect.DeepEqual(p.VersionLabels(), want) {
		t.Fatalf("VersionLabels = %#v, want %#v", p.VersionLabels(), want)
	}
	if p.ErrorProjector() == nil {
		t.Fatal("ErrorProjector must be non-nil")
	}
	if p.InternalErrorProjector() == nil {
		t.Fatal("InternalErrorProjector must be non-nil")
	}
	m := p.Manifest()
	type expectedRoute struct {
		method      string
		path        string
		gateMode    dialect.GateMode
		permissions []constants.Permission
	}
	none := func(method, path string) expectedRoute {
		return expectedRoute{method: method, path: path, gateMode: dialect.GateNone}
	}
	modifyMetadata := func(method, path string) expectedRoute {
		return expectedRoute{
			method:      method,
			path:        path,
			gateMode:    dialect.GateAll,
			permissions: []constants.Permission{constants.PermModifyMetadata},
		}
	}
	customizeApplication := func(method, path string) expectedRoute {
		return expectedRoute{
			method:      method,
			path:        path,
			gateMode:    dialect.GateAll,
			permissions: []constants.Permission{constants.PermCustomizeApplication},
		}
	}
	modifyAllData := func(method, path string) expectedRoute {
		return expectedRoute{
			method:      method,
			path:        path,
			gateMode:    dialect.GateAll,
			permissions: []constants.Permission{constants.PermModifyAllData},
		}
	}
	viewFieldAuditTrail := func(method, path string) expectedRoute {
		return expectedRoute{
			method:      method,
			path:        path,
			gateMode:    dialect.GateAll,
			permissions: []constants.Permission{constants.PermViewFieldAuditTrail},
		}
	}
	viewSetup := func(method, path string) expectedRoute {
		return expectedRoute{
			method:      method,
			path:        path,
			gateMode:    dialect.GateAll,
			permissions: []constants.Permission{constants.PermViewSetup},
		}
	}
	viewSetupOrCustomize := func(method, path string) expectedRoute {
		return expectedRoute{
			method:      method,
			path:        path,
			gateMode:    dialect.GateAny,
			permissions: []constants.Permission{constants.PermViewSetup, constants.PermCustomizeApplication},
		}
	}
	runReports := func(method, path string) expectedRoute {
		return expectedRoute{
			method:      method,
			path:        path,
			gateMode:    dialect.GateAll,
			permissions: []constants.Permission{constants.PermRunReports},
		}
	}
	manageFolders := func(method, path string) expectedRoute {
		return expectedRoute{
			method:      method,
			path:        path,
			gateMode:    dialect.GateAny,
			permissions: []constants.Permission{constants.PermManageReportsInPubFolders, constants.PermManageDashbdsInPubFolders},
		}
	}
	manageUsers := func(method, path string) expectedRoute {
		return expectedRoute{
			method:      method,
			path:        path,
			gateMode:    dialect.GateAll,
			permissions: []constants.Permission{constants.PermManageUsers},
		}
	}
	runFlow := func(method, path string) expectedRoute {
		return expectedRoute{
			method:      method,
			path:        path,
			gateMode:    dialect.GateAll,
			permissions: []constants.Permission{constants.PermRunFlow},
		}
	}
	manageNamedCredentials := func(method, path string) expectedRoute {
		return expectedRoute{
			method:      method,
			path:        path,
			gateMode:    dialect.GateAll,
			permissions: []constants.Permission{constants.PermManageNamedCredentials},
		}
	}
	wantRoutes := []expectedRoute{
		none("GET", "/id/:orgId/:userId"),
		none("GET", "/connect/notifications"),
		none("GET", "/actions/standard"),
		none("POST", "/actions/standard/:name"),
		none("POST", "/actions/standard/:name/preview"),
		none("POST", "/actions/standard/:name/output-preview"),
		none("GET", "/quickActions"),
		none("GET", "/sobjects/:sobject/quickActions"),
		none("GET", "/quickActions/:actionName/describe"),
		none("POST", "/quickActions/:actionName"),
		runFlow("GET", "/actions/custom/flow"),
		runFlow("GET", "/actions/custom/flow/:name"),
		runFlow("POST", "/actions/custom/flow/:name"),
		runFlow("POST", "/actions/custom/resumeFlow"),
		none("GET", "/sobjects/FlowInterview"),
		none("GET", "/sobjects/FlowInterview/:id"),
		none("DELETE", "/sobjects/FlowInterview/:id"),
		none("POST", "/process/approvals"),
		none("GET", "/process/approvals/pending"),
		none("GET", "/process/approvals/records/:recordId/instance"),
		none("GET", "/process/approvals/records/:recordId/step-display"),
		none("GET", "/process/rules"),
		none("GET", "/process/rules/:sObject"),
		modifyMetadata("GET", "/process/approvals/notification-flows"),
		modifyMetadata("POST", "/process/approvals/clone"),
		none("POST", "/jobs/query"),
		none("GET", "/jobs/query/:jobId"),
		none("GET", "/jobs/query/:jobId/results"),
		runReports("GET", "/analytics/dashboards"),
		runReports("GET", "/analytics/dashboards/:dashboardId"),
		runReports("POST", "/analytics/dashboards"),
		runReports("PATCH", "/analytics/dashboards/:dashboardId"),
		runReports("PUT", "/analytics/dashboards/:dashboardId"),
		runReports("DELETE", "/analytics/dashboards/:dashboardId"),
		runReports("POST", "/analytics/dashboards/:dashboardId/refresh"),
		runReports("GET", "/analytics/dashboards/:dashboardId/status"),
		runReports("GET", "/analytics/reports"),
		runReports("POST", "/analytics/reports"),
		runReports("POST", "/analytics/report-execution"),
		runReports("POST", "/analytics/reports/execute"),
		runReports("GET", "/analytics/reports/:reportId"),
		runReports("POST", "/analytics/reports/:reportId"),
		runReports("PATCH", "/analytics/reports/:reportId"),
		runReports("DELETE", "/analytics/reports/:reportId"),
		runReports("GET", "/analytics/reports/:reportId/describe"),
		runReports("GET", "/folders"),
		manageFolders("POST", "/folders"),
		manageUsers("POST", "/folders/transfer-ownership"),
		runReports("GET", "/folders/:id"),
		manageFolders("PATCH", "/folders/:id"),
		manageFolders("DELETE", "/folders/:id"),
		runReports("GET", "/folders/:id/children"),
		runReports("GET", "/folders/:id/shares"),
		manageFolders("POST", "/folders/:id/shares"),
		manageFolders("PUT", "/folders/:id/shares"),
		runReports("GET", "/folders/:id/shares/:shareId"),
		manageFolders("PATCH", "/folders/:id/shares/:shareId"),
		manageFolders("DELETE", "/folders/:id/shares/:shareId"),
		none("GET", "/permissions/effective"),
		none("GET", "/metadata/objects"),
		none("GET", "/metadata/objects/:apiName"),
		none("GET", "/metadata/fieldtypes"),
		none("GET", "/metadata/validation-rules"),
		none("GET", "/metadata/search/scope-order"),
		none("GET", "/metadata/formula-views/:objectName/status"),
		none("GET", "/metadata/formula-views/:objectName/ddl"),
		modifyMetadata("GET", "/metadata/deployRequest/:id"),
		modifyMetadata("POST", "/metadata/objects"),
		modifyMetadata("PATCH", "/metadata/objects/:apiName"),
		modifyMetadata("DELETE", "/metadata/objects/:apiName"),
		modifyMetadata("POST", "/metadata/objects/:apiName/fields"),
		modifyMetadata("POST", "/metadata/objects/:apiName/fields/batch"),
		modifyMetadata("PATCH", "/metadata/objects/:apiName/fields/:fieldApiName"),
		modifyMetadata("DELETE", "/metadata/objects/:apiName/fields/:fieldApiName"),
		modifyMetadata("POST", "/metadata/refresh-cache"),
		modifyMetadata("POST", "/metadata/deployRequest"),
		modifyMetadata("POST", "/metadata/formula-views/:objectName/rebuild"),
		modifyMetadata("POST", "/metadata/formula-views/rebuild-all"),
		modifyMetadata("POST", "/metadata/formula-views/:objectName/validate"),
		customizeApplication("POST", "/admin/autonumber/refresh-metadata"),
		none("GET", "/admin/connector/sync-status"),
		viewSetup("GET", "/connector-webhooks/secrets"),
		manageNamedCredentials("POST", "/connector-webhooks/secrets/:id/rotate"),
		viewFieldAuditTrail("GET", "/admin/audit/log"),
		modifyAllData("POST", "/admin/recalculate-rollups/:object"),
		modifyMetadata("POST", "/metadata/validation-rules"),
		modifyMetadata("PATCH", "/metadata/validation-rules/:id"),
		modifyMetadata("DELETE", "/metadata/validation-rules/:id"),
		customizeApplication("POST", "/tooling/sobjects/CustomObject"),
		customizeApplication("PATCH", "/tooling/sobjects/CustomObject/:id"),
		customizeApplication("POST", "/tooling/sobjects/CustomField"),
		customizeApplication("POST", "/tooling/sobjects/ValidationRule"),
		customizeApplication("POST", "/tooling/sobjects/QuickActionDefinition"),
		viewSetupOrCustomize("GET", "/tooling/query"),
		customizeApplication("GET", "/tooling/sobjects/FlowDefinition"),
		customizeApplication("GET", "/tooling/sobjects/FlowDefinition/:id"),
		customizeApplication("POST", "/tooling/sobjects/FlowDefinition"),
		customizeApplication("PATCH", "/tooling/sobjects/FlowDefinition/:id"),
		customizeApplication("DELETE", "/tooling/sobjects/FlowDefinition/:id"),
		customizeApplication("GET", "/tooling/sobjects/Flow"),
		customizeApplication("GET", "/tooling/sobjects/Flow/:id"),
		customizeApplication("POST", "/tooling/sobjects/Flow"),
		customizeApplication("PATCH", "/tooling/sobjects/Flow/:id"),
		customizeApplication("DELETE", "/tooling/sobjects/Flow/:id"),
		customizeApplication("POST", "/tooling/sobjects/Flow/:id/activate"),
		customizeApplication("POST", "/tooling/sobjects/Flow/:id/deactivate"),
		none("GET", "/ui-api/record-ui/:recordIds"),
		none("POST", "/ui-api/record-access"),
		none("GET", "/ui-api/object-info/:objectApiName"),
		none("GET", "/ui-api/layout/:objectApiName"),
		none("GET", "/ui-api/layouts/:objectApiName"),
		none("GET", "/ui-api/approval-layouts/:objectApiName"),
		none("GET", "/ui-api/record-defaults/create/:objectApiName"),
		none("GET", "/ui-api/record-defaults/clone/:recordId"),
		none("GET", "/ui-api/list-info/:objectApiName/:listViewApiName"),
		none("GET", "/ui-api/layout-assignments/:objectApiName"),
		none("GET", "/ui-api/apps"),
		customizeApplication("GET", "/tooling/sobjects/CustomApplication"),
		customizeApplication("GET", "/tooling/sobjects/CustomApplication/:id"),
		customizeApplication("POST", "/tooling/sobjects/CustomApplication"),
		customizeApplication("PATCH", "/tooling/sobjects/CustomApplication/:id"),
		customizeApplication("DELETE", "/tooling/sobjects/CustomApplication/:id"),
		customizeApplication("PATCH", "/tooling/sobjects/ValidationRule/:id"),
		customizeApplication("DELETE", "/tooling/sobjects/ValidationRule/:id"),
		none("GET", "/sobjects"),
		none("GET", "/sobjects/_System_File/describe"),
		none("GET", "/sobjects/_System_File/describe/layouts"),
		none("GET", "/sobjects/:sobject/describe"),
		none("GET", "/sobjects/:sobject/describe/layouts"),
		none("GET", "/sobjects/:sobject/describe/layouts/:recordTypeId"),
		none("GET", "/sobjects/:sobject/listviews"),
		none("POST", "/sobjects/:sobject/listviews"),
		none("DELETE", "/sobjects/:sobject/listviews/:listViewId"),
		none("GET", "/sobjects/:sobject/listviews/:listViewId/describe"),
		none("GET", "/sobjects/relevantItems"),
		none("POST", "/files"),
		none("POST", "/record-files"),
		none("GET", "/files/:id/download"),
		none("DELETE", "/files/:id"),
		none("GET", "/sobjects/_System_File/:id/download"),
		none("DELETE", "/sobjects/_System_File/:id"),
		none("POST", "/sobjects/ContentVersion"),
		none("GET", "/sobjects/ContentVersion/:fileId/VersionData"),
		none("GET", "/sobjects/:sobject/:id"),
		none("POST", "/sobjects/:sobject"),
		none("PATCH", "/sobjects/:sobject/:id"),
		none("DELETE", "/sobjects/:sobject/:id"),
		none("PATCH", "/sobjects/:sobject/:id/:fieldValue"),
		none("GET", "/sobjects/:sobject/:id/:fieldValue"),
		none("GET", "/query"),
		none("GET", "/queryAll"),
		none("GET", "/query/:locator"),
		none("POST", "/ui-api/related-list-counts"),
		none("GET", "/search"),
		none("GET", "/parameterizedSearch"),
		none("POST", "/parameterizedSearch"),
		none("GET", "/search/layout"),
		none("GET", "/search/suggestions"),
		none("GET", "/search/scopeOrder"),
		none("GET", "/recyclebin"),
		none("DELETE", "/recyclebin/:id"),
		none("POST", "/recyclebin/restore/:id"),
		none("POST", "/composite"),
		none("POST", "/composite/batch"),
		none("POST", "/composite/tree/:objectName"),
		none("GET", "/composite/sobjects"),
		none("POST", "/composite/sobjects"),
		none("PATCH", "/composite/sobjects"),
		none("DELETE", "/composite/sobjects"),
		none("PATCH", "/composite/sobjects/:sObjectName/:externalIdFieldName"),
	}
	if len(m) != len(wantRoutes) {
		t.Fatalf("Manifest = %#v", m)
	}
	for i, want := range wantRoutes {
		if m[i].Method != want.method || m[i].Path != want.path {
			t.Fatalf("Manifest[%d] = %s %s, want %s %s", i, m[i].Method, m[i].Path, want.method, want.path)
		}
		if m[i].Gate.Mode != want.gateMode || !reflect.DeepEqual(m[i].Gate.Permissions, want.permissions) {
			t.Fatalf("Gate[%d] = %#v, want mode=%v permissions=%v", i, m[i].Gate, want.gateMode, want.permissions)
		}
		if m[i].Handler == nil {
			t.Fatalf("Manifest[%d] Handler is nil", i)
		}
	}
}
