package dialectsfcrm

import (
	"encoding/json"
	"io"
	"os"
	"reflect"
	"sort"
	"strings"
	"testing"
)

type d2aReadClosure struct {
	Schema         string          `json:"schema"`
	Distribution   string          `json:"distribution"`
	ScopeRule      string          `json:"scope_rule"`
	Families       []d2aReadFamily `json:"families"`
	ExcludedRoutes []string        `json:"excluded_routes"`
	ExclusionOwner string          `json:"exclusion_owner"`
}

type d2aReadFamily struct {
	ID                     string   `json:"id"`
	Status                 string   `json:"status"`
	CurrentOwner           string   `json:"current_owner"`
	TargetOwner            string   `json:"target_owner"`
	TargetCapabilityFamily string   `json:"target_capability_family"`
	ParityGate             string   `json:"parity_gate"`
	DeletionTarget         string   `json:"deletion_target"`
	Routes                 []string `json:"routes"`
	ProductRoutes          []string `json:"product_routes,omitempty"`
}

func TestD2ASynchronousReadClosureClassifiesTheActiveManifestExactlyOnce(t *testing.T) {
	closure := loadD2AReadClosure(t)
	if closure.Schema != "nexuscrm.sfcrm-synchronous-read-closure.d2a/v1" ||
		closure.Distribution != "salesforce-v63" || closure.ScopeRule == "" || closure.ExclusionOwner == "" {
		t.Fatalf("incomplete D2A closure identity: %#v", closure)
	}

	classified := make(map[string]string)
	productClassified := make(map[string]string)
	allowedStatus := map[string]bool{
		"existing-neutral-owner":    true,
		"proven-d1":                 true,
		"proven-d2a-query":          true,
		"proven-d2a-related-counts": true,
		"proven-d2a-notifications":  true,
		"proven-d2a-recycle-read":   true,
		"activation-pending":        true,
		"planned":                   true,
	}
	for _, family := range closure.Families {
		if family.ID == "" || !allowedStatus[family.Status] || family.CurrentOwner == "" ||
			family.TargetOwner == "" || family.TargetCapabilityFamily == "" || family.ParityGate == "" ||
			family.DeletionTarget == "" || len(family.Routes)+len(family.ProductRoutes) == 0 {
			t.Fatalf("incomplete D2A family: %#v", family)
		}
		for _, route := range family.Routes {
			classifyD2ARoute(t, classified, route, family.ID)
		}
		for _, route := range family.ProductRoutes {
			classifyD2ARoute(t, productClassified, route, family.ID)
		}
	}
	for _, route := range closure.ExcludedRoutes {
		classifyD2ARoute(t, classified, route, "excluded-from-d2a")
	}

	manifest := NewCompositeTreePartition().Manifest()
	active := make(map[string]bool, len(manifest))
	for _, route := range manifest {
		key := route.Method + " " + route.Path
		if active[key] {
			t.Fatalf("active CRM manifest declares %q more than once", key)
		}
		active[key] = true
		if classified[key] == "" {
			t.Fatalf("active CRM route %q has no D2A or explicit exclusion owner", key)
		}
	}
	for route, owner := range classified {
		if !active[route] {
			t.Fatalf("D2A closure classifies absent CRM route %q as %q", route, owner)
		}
	}
	if len(classified) != len(active) {
		t.Fatalf("D2A closure routes=%d active manifest routes=%d", len(classified), len(active))
	}
	if owner := productClassified["GET /product/notifications"]; owner != "notification-query" {
		t.Fatalf("live product notification route owner=%q, want notification-query", owner)
	}
}

func TestD2ANotificationClosurePinsBothAliasesToTheProvenQueryOwner(t *testing.T) {
	closure := loadD2AReadClosure(t)
	for _, family := range closure.Families {
		if family.ID != "notification-query" {
			continue
		}
		if family.Status != "proven-d2a-notifications" || family.TargetCapabilityFamily != queryCapabilityIDForClosure ||
			!reflect.DeepEqual(family.Routes, []string{"GET /connect/notifications"}) ||
			!reflect.DeepEqual(family.ProductRoutes, []string{"GET /product/notifications"}) ||
			!strings.Contains(family.ParityGate, "29892899936") ||
			!strings.Contains(family.DeletionTarget, "29892899936") {
			t.Fatalf("notification family overclaims proof or omits the live product alias: %#v", family)
		}
		return
	}
	t.Fatal("notification family is absent from the D2A closure")
}

func TestD2AReadLikeNonGETRoutesCannotDisappearIntoTheMutationExclusion(t *testing.T) {
	closure := loadD2AReadClosure(t)
	readOwners := make(map[string]string)
	for _, family := range closure.Families {
		for _, route := range family.Routes {
			readOwners[route] = family.ID
		}
	}

	want := []string{
		"POST /actions/standard/:name/preview",
		"POST /actions/standard/:name/output-preview",
		"POST /analytics/dashboards/:dashboardId/refresh",
		"POST /analytics/report-execution",
		"POST /analytics/reports/execute",
		"POST /analytics/reports/:reportId",
		"POST /ui-api/record-access",
		"POST /ui-api/related-list-counts",
		"POST /parameterizedSearch",
		"POST /composite",
		"POST /composite/batch",
	}
	for _, route := range want {
		if readOwners[route] == "" {
			t.Fatalf("read-like route %q is not owned by a D2A family", route)
		}
	}
	for _, route := range closure.ExcludedRoutes {
		if strings.HasPrefix(route, "GET ") {
			t.Fatalf("synchronous GET %q was hidden in the D2A exclusion list", route)
		}
	}
}

func TestD2AFirstFamilyClosureMatchesTheSingleL1RouteOwner(t *testing.T) {
	closure := loadD2AReadClosure(t)
	var got []string
	for _, family := range closure.Families {
		if family.ID != "record-query-list" {
			continue
		}
		if family.Status != "proven-d2a-query" || family.TargetCapabilityFamily != queryCapabilityIDForClosure {
			t.Fatalf("query family overclaims activation or changed capability: %#v", family)
		}
		got = append(got, family.Routes...)
	}
	var want []string
	for _, route := range runtimeQueryRoutes() {
		want = append(want, route.Method+" "+route.Path)
	}
	sort.Strings(got)
	sort.Strings(want)
	if !reflect.DeepEqual(got, want) {
		t.Fatalf("D2A query closure=%v single L1 route owner=%v", got, want)
	}
}

func TestD2ARelatedCountClosureMatchesTheSingleL1RouteOwner(t *testing.T) {
	closure := loadD2AReadClosure(t)
	for _, family := range closure.Families {
		if family.ID != "related-record-count" {
			continue
		}
		if family.Status != "proven-d2a-related-counts" ||
			family.TargetCapabilityFamily != relatedCountCapabilityIDForClosure ||
			!reflect.DeepEqual(family.Routes, []string{"POST /ui-api/related-list-counts"}) {
			t.Fatalf("related-count family overclaims proof or changed ownership: %#v", family)
		}
		return
	}
	t.Fatal("related-count family is absent from the D2A closure")
}

func TestD2ARecycleBinClosurePinsTheProvenSingleL1Owner(t *testing.T) {
	closure := loadD2AReadClosure(t)
	for _, family := range closure.Families {
		if family.ID != "recycle-read" {
			continue
		}
		if family.Status != "proven-d2a-recycle-read" || family.TargetCapabilityFamily != recycleBinCapabilityIDForClosure ||
			!reflect.DeepEqual(family.Routes, []string{"GET /recyclebin"}) ||
			!strings.Contains(family.ParityGate, "29912105907") ||
			!strings.Contains(family.DeletionTarget, "29912105907") {
			t.Fatalf("recycle-bin family overclaims proof or changed ownership: %#v", family)
		}
		return
	}
	t.Fatal("recycle-bin family is absent from the D2A closure")
}

func TestD2ABulkIngestReadClosurePinsTheSingleL1Owner(t *testing.T) {
	closure := loadD2AReadClosure(t)
	for _, family := range closure.Families {
		if family.ID != "bulk-ingest-job-read" {
			continue
		}
		if family.Status != "activation-pending" || family.CurrentOwner != "sfcrm AppService and Runtime Bulk Ingest adapter" ||
			family.TargetCapabilityFamily != bulkIngestCapabilityIDForClosure ||
			len(family.Routes) != 0 || !reflect.DeepEqual(family.ProductRoutes, []string{"GET /jobs/ingest/:jobId"}) ||
			!strings.Contains(family.ParityGate, "test2") {
			t.Fatalf("Bulk Ingest read family overclaims proof or lost its AppService owner: %#v", family)
		}
		return
	}
	t.Fatal("Bulk Ingest read family is absent from the D2A closure")
}

func TestD2ARecordDefaultsReadClosurePinsTheSingleL1Owner(t *testing.T) {
	closure := loadD2AReadClosure(t)
	for _, family := range closure.Families {
		if family.ID != "record-defaults-read" {
			continue
		}
		if family.Status != "activation-pending" ||
			family.CurrentOwner != "sfcrm AppService over Runtime Record View and create-default preview adapters" ||
			family.TargetCapabilityFamily != recordDefaultsCapabilityIDForClosure ||
			!reflect.DeepEqual(family.Routes, []string{
				"GET /ui-api/record-defaults/create/:objectApiName",
				"GET /ui-api/record-defaults/clone/:recordId",
			}) ||
			len(family.ProductRoutes) != 0 || !strings.Contains(family.ParityGate, "test2") {
			t.Fatalf("Record Defaults family overclaims proof or lost its AppService owner: %#v", family)
		}
		return
	}
	t.Fatal("Record Defaults family is absent from the D2A closure")
}

// The picker moved outside the legacy manifest. Preserve its migration owner
// without misclassifying it as an active dialect route or claiming deployment.
func TestD2ACandidateClosureTracksAppServiceOutsideLegacyManifest(t *testing.T) {
	for _, family := range loadD2AReadClosure(t).Families {
		if family.ID != "related-list-candidates" {
			continue
		}
		if family.Status != "activation-pending" || family.TargetCapabilityFamily != "io.nexuscrm.sfcrm.effective-permissions.v1" ||
			len(family.Routes) != 0 || !reflect.DeepEqual(family.ProductRoutes, []string{"GET /ui-api/related-list-candidates/:objectApiName"}) ||
			!strings.Contains(family.CurrentOwner, "sfcrm AppService") || !strings.Contains(family.ParityGate, "test2") {
			t.Fatalf("candidate closure lost its owner or overclaims activation: %#v", family)
		}
		return
	}
	t.Fatal("related-list candidate AppService owner is absent")
}

// QueryCapabilityIDForClosure is intentionally test-only: the serving capability
// remains owned by the AppRelease/AppService, while this evidence must pin the
// exact family selected for the first D2A activation.
const queryCapabilityIDForClosure = "io.nexuscrm.sfcrm.query.v1"

const relatedCountCapabilityIDForClosure = "io.nexuscrm.sfcrm.related-list-counts.v1"

const recycleBinCapabilityIDForClosure = "io.nexuscrm.sfcrm.recycle-bin-read.v1"

const bulkIngestCapabilityIDForClosure = "io.nexuscrm.sfcrm.bulk-ingest.v1"

const recordDefaultsCapabilityIDForClosure = "io.nexuscrm.sfcrm.ui.record-defaults.v1"

func loadD2AReadClosure(t *testing.T) d2aReadClosure {
	t.Helper()
	file, err := os.Open("testdata/d2a-synchronous-read-closure.json")
	if err != nil {
		t.Fatal(err)
	}
	defer file.Close()
	decoder := json.NewDecoder(file)
	decoder.DisallowUnknownFields()
	var closure d2aReadClosure
	if err := decoder.Decode(&closure); err != nil {
		t.Fatalf("decode D2A closure: %v", err)
	}
	if err := decoder.Decode(&struct{}{}); err != io.EOF {
		t.Fatalf("D2A closure has trailing content: %v", err)
	}
	return closure
}

func classifyD2ARoute(t *testing.T, classified map[string]string, route, owner string) {
	t.Helper()
	parts := strings.SplitN(route, " ", 2)
	if len(parts) != 2 || parts[0] == "" || parts[1] == "" || !strings.HasPrefix(parts[1], "/") {
		t.Fatalf("invalid D2A route %q owned by %q", route, owner)
	}
	if previous := classified[route]; previous != "" {
		t.Fatalf("D2A route %q has two owners: %q and %q", route, previous, owner)
	}
	classified[route] = owner
}
