package dialectsfcrm

import (
	"context"
	"encoding/json"
	"reflect"
	"strings"
	"testing"

	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
)

func TestNewApprovalProcessComponentRegistrationIsCompleteAndBounded(t *testing.T) {
	registration, err := NewApprovalProcessComponentRegistration()
	if err != nil {
		t.Fatal(err)
	}
	if registration.Contract.Kind != cp.ComponentKindApprovalProcess || registration.Capability.Kind != cp.ComponentKindApprovalProcess || !registration.Capability.Installable {
		t.Fatalf("registration kind/capability = %#v / %#v", registration.Contract, registration.Capability)
	}
	if registration.Contract.OwnerDiscriminator != cp.ComponentOwnerStrategyOwnerPack || registration.Contract.Phase != cp.InstallPlanPhaseAutomation {
		t.Fatalf("registration contract = %#v", registration.Contract)
	}
	wantTables := []string{"ProcessDefinition", "ProcessNode", "ProcessAction"}
	if len(registration.Contract.WriteSet) != len(wantTables) {
		t.Fatalf("write set = %#v", registration.Contract.WriteSet)
	}
	for index, want := range wantTables {
		if got := registration.Contract.WriteSet[index].Table; got != want {
			t.Fatalf("write set %d table = %q, want %q", index, got, want)
		}
	}
	if registration.ManagedProvider == nil || registration.BuildHandler == nil {
		t.Fatal("registration did not retain the complete provider and build handler")
	}
	if err := registration.ManagedProvider.Validate(); err != nil {
		t.Fatalf("managed provider validation: %v", err)
	}
	if _, ok := registration.Contract.Manageabilities[cp.ManageabilityLocked]; !ok {
		t.Fatalf("contract manageabilities = %#v", registration.Contract.Manageabilities)
	}
	if len(registration.Contract.Manageabilities) != 1 || len(registration.Capability.Manageabilities) != 1 {
		t.Fatalf("registration manageabilities are not locked-only: %#v / %#v", registration.Contract.Manageabilities, registration.Capability.Manageabilities)
	}
	if _, ok := registration.Capability.Manageabilities[cp.ManageabilityLocked]; !ok {
		t.Fatalf("capability manageabilities = %#v", registration.Capability.Manageabilities)
	}
	if len(registration.AllowedCategories) != 1 || registration.AllowedCategories[0] != cp.AppPack {
		t.Fatalf("allowed categories = %#v", registration.AllowedCategories)
	}
}

func TestNewApprovalProcessManagedLifecycleRegistrationIsAdditiveAndTargetCanonical(t *testing.T) {
	base, lifecycle, err := NewApprovalProcessManagedLifecycleRegistration()
	if err != nil {
		t.Fatal(err)
	}
	if base.ManagedProvider != nil {
		t.Fatal("new approval lifecycle base retained an active five-callback provider")
	}
	if lifecycle.Kind() != cp.ComponentKindApprovalProcess || lifecycle.TransactionMode() != cp.ComponentManagedTransactionPessimisticReadCommitted {
		t.Fatalf("lifecycle identity = %q/%q", lifecycle.Kind(), lifecycle.TransactionMode())
	}
	if got := lifecycle.ReadSet(); len(got) != 3 || got[0].Table != "ProcessAction" || got[1].Table != "ProcessDefinition" || got[2].Table != "ProcessNode" {
		t.Fatalf("read set = %#v", got)
	}
	request := approvalProcessManagedRequestForTest(t)
	plan, err := lifecycle.Lifecycle().Plan(
		context.Background(), cp.ComponentManagedScopeItem, cp.NewComponentManagedItemLifecycleRequest(request),
		approvalProcessManagedLifecycleTestReadStore{},
	)
	if err != nil {
		t.Fatal(err)
	}
	targets := plan.Targets()
	if len(targets) != 2 || targets[0].TargetDomain != approvalProcessManagedTargetDomain || string(targets[0].PhysicalKeyBytes) != "quote_approval" || targets[1].TargetDomain != approvalProcessManagedSetTargetDomain || !reflect.DeepEqual(targets[1].PhysicalKeyBytes, []byte{0}) {
		t.Fatalf("item targets = %#v", targets)
	}
	if string(plan.ProjectionIdentity()) != "approval_process\x00sales\x00Quote/Quote_Approval" {
		t.Fatalf("projection identity = %q", plan.ProjectionIdentity())
	}
	if err := lifecycle.ValidatePlanCapabilities(plan); err != nil {
		t.Fatal(err)
	}
	legacy, err := NewApprovalProcessComponentRegistration()
	if err != nil || legacy.ManagedProvider == nil {
		t.Fatalf("legacy compatibility registration = %#v, %v", legacy, err)
	}
}

type approvalProcessManagedLifecycleTestReadStore struct{}

func (approvalProcessManagedLifecycleTestReadStore) ReadRows(context.Context, cp.ComponentManagedRowQuery) ([]cp.ComponentRow, error) {
	return nil, nil
}

func TestApprovalProcessManagedRegistrationWiresAllLifecycleCallbacks(t *testing.T) {
	registration, err := NewApprovalProcessComponentRegistration()
	if err != nil {
		t.Fatal(err)
	}
	provider, ctx := registration.ManagedProvider, context.Background()
	request, setRequest := cp.ComponentManagedRequest{}, cp.ComponentManagedSetRequest{}
	if err := provider.Preflight(ctx, nil, request); err == nil || !strings.Contains(err.Error(), "preflight row reader") {
		t.Fatalf("preflight callback error = %v", err)
	}
	if err := provider.Apply(ctx, nil, request); err == nil || !strings.Contains(err.Error(), "apply mutation store") {
		t.Fatalf("apply callback error = %v", err)
	}
	if _, _, err := provider.VerifyProjection(ctx, nil, request); err == nil || !strings.Contains(err.Error(), "verify row reader") {
		t.Fatalf("verify callback error = %v", err)
	}
	if _, err := provider.ListLiveBusinessKeys(ctx, nil, setRequest); err == nil || !strings.Contains(err.Error(), "live-key row reader") {
		t.Fatalf("list callback error = %v", err)
	}
	if err := provider.ReconcileSet(ctx, nil, setRequest, nil); err == nil || !strings.Contains(err.Error(), "reconcile mutation store") {
		t.Fatalf("reconcile callback error = %v", err)
	}
}

func TestApprovalProcessBuildHandlerRecordsLosslessManagedProjection(t *testing.T) {
	raw := " \n<ApprovalProcess><label>Quote approval</label><active>true</active>" +
		"<approvalStep><name>Review</name><assignedApprover><approver><type>user</type><name>first@example.com</name></approver></assignedApprover></approvalStep>" +
		"</ApprovalProcess>\n"
	buildContext := &approvalProcessTestBuildContext{packName: "sales"}
	registration, err := NewApprovalProcessComponentRegistration()
	if err != nil {
		t.Fatal(err)
	}
	err = registration.BuildHandler(buildContext, cp.ComponentBuildRequest{
		PackName: "sales",
		Component: cp.ManifestComponent{
			Kind:          cp.ComponentKindApprovalProcess,
			BusinessKey:   "Quote/Quote_Approval",
			Manageability: cp.ManageabilityLocked,
			Payload:       []byte(raw),
		},
		Payload: []byte(raw),
	})
	if err != nil {
		t.Fatal(err)
	}
	if buildContext.managed == nil || buildContext.managed.PublishRuntimeStartupProjection {
		t.Fatalf("managed declaration = %#v", buildContext.managed)
	}
	if err := buildContext.managed.Validate(); err != nil {
		t.Fatalf("managed declaration validation: %v", err)
	}
	var plan ApprovalProcessPersistencePlan
	if err := json.Unmarshal(buildContext.managed.ExpectedProjection, &plan); err != nil {
		t.Fatal(err)
	}
	if plan.Parent.BusinessKey != (ApprovalProcessBusinessKey{"Quote", "Quote_Approval"}) || plan.Parent.OwnerPack != "sales" || plan.Parent.RawMetadata != raw {
		t.Fatalf("expected projection parent = %#v", plan.Parent)
	}
	if len(plan.Nodes) != 1 || plan.Nodes[0].ApproverType != "user" || !reflect.DeepEqual(plan.Nodes[0].ApproverReferences, []string{"first@example.com"}) {
		t.Fatalf("expected projection nodes = %#v", plan.Nodes)
	}
	if got := buildContext.managed.CollisionClaims; len(got) != 1 || got[0].Namespace != approvalProcessDeveloperNameCollision || got[0].Key != "quote_approval" {
		t.Fatalf("collision claims = %#v", got)
	}
	otherObject := &approvalProcessTestBuildContext{packName: "sales"}
	if err := registration.BuildHandler(otherObject, cp.ComponentBuildRequest{
		PackName:  "sales",
		Component: cp.ManifestComponent{Kind: cp.ComponentKindApprovalProcess, BusinessKey: "Account/Quote_Approval", Manageability: cp.ManageabilityLocked},
		Payload:   []byte(raw),
	}); err != nil {
		t.Fatal(err)
	}
	if got := otherObject.managed.CollisionClaims; len(got) != 1 || got[0] != buildContext.managed.CollisionClaims[0] {
		t.Fatalf("cross-object collision claims = %#v, want %#v", got, buildContext.managed.CollisionClaims)
	}

	badContext := &approvalProcessTestBuildContext{packName: "other"}
	if err := registration.BuildHandler(badContext, cp.ComponentBuildRequest{PackName: "sales", Component: cp.ManifestComponent{Kind: cp.ComponentKindApprovalProcess}}); err == nil || !strings.Contains(err.Error(), "does not match") {
		t.Fatalf("pack mismatch error = %v", err)
	}
}

func TestApprovalProcessBuildHandlerRejectsNonCanonicalOrConflictingIdentity(t *testing.T) {
	raw := []byte(`<ApprovalProcess><label>Quote approval</label><active>true</active></ApprovalProcess>`)
	registration, err := NewApprovalProcessComponentRegistration()
	if err != nil {
		t.Fatal(err)
	}
	baseRequest := func() cp.ComponentBuildRequest {
		return cp.ComponentBuildRequest{
			PackName: "sales",
			Component: cp.ManifestComponent{
				Kind:          cp.ComponentKindApprovalProcess,
				BusinessKey:   "Quote/Quote_Approval",
				Manageability: cp.ManageabilityLocked,
				Payload:       append([]byte(nil), raw...),
			},
			Payload: append([]byte(nil), raw...),
		}
	}
	tests := []struct {
		name        string
		contextPack string
		mutate      func(*cp.ComponentBuildRequest)
		want        string
	}{
		{name: "context pack", contextPack: " sales", want: "context pack name"},
		{name: "request pack", contextPack: "sales", mutate: func(r *cp.ComponentBuildRequest) { r.PackName = "sales " }, want: "does not match"},
		{name: "business key", contextPack: "sales", mutate: func(r *cp.ComponentBuildRequest) { r.Component.BusinessKey = " Quote/Quote_Approval" }, want: "must be canonical"},
		{name: "manageability", contextPack: "sales", mutate: func(r *cp.ComponentBuildRequest) { r.Component.Manageability = cp.Manageability("unlocked") }, want: "manageability must be"},
		{name: "conflicting component payload", contextPack: "sales", mutate: func(r *cp.ComponentBuildRequest) { r.Component.Payload = []byte(`{}`) }, want: "must exactly match"},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			request := baseRequest()
			if test.mutate != nil {
				test.mutate(&request)
			}
			err := registration.BuildHandler(&approvalProcessTestBuildContext{packName: test.contextPack}, request)
			if err == nil || !strings.Contains(err.Error(), test.want) {
				t.Fatalf("BuildHandler() error = %v, want containing %q", err, test.want)
			}
		})
	}
	pathBacked := baseRequest()
	pathBacked.Component.Payload = nil
	if err := registration.BuildHandler(&approvalProcessTestBuildContext{packName: "sales"}, pathBacked); err != nil {
		t.Fatalf("path-backed request with resolved payload: %v", err)
	}
}

type approvalProcessTestBuildContext struct {
	packName string
	managed  *cp.ComponentBuildManagedComponent
}

func (c *approvalProcessTestBuildContext) PackName() string { return c.packName }
func (*approvalProcessTestBuildContext) LookupObjectSchema(string) (cp.ComponentObjectSchema, bool) {
	return cp.ComponentObjectSchema{}, false
}
func (c *approvalProcessTestBuildContext) AddManagedComponent(managed cp.ComponentBuildManagedComponent) error {
	c.managed = &managed
	return nil
}
func (*approvalProcessTestBuildContext) AddRuntimeStartupShallowDeclaration(cp.ComponentKind, string, []byte) error {
	return nil
}
