package packs

import (
	"context"
	"regexp"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	cp "github.com/pingcap-inc/nexuscrm-contract/pkg/controlplane"
)

func TestManagedComponentHostRetainsReadCallbacksAndFailsClosedForMutation(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = db.Close() })
	catalog := testComponentColumnCatalog()
	mutationCalls := 0
	provider := completeManagedComponentProvider()
	read := func(ctx context.Context, reader cp.ComponentRowReader) error {
		_, err := reader.ReadRows(ctx, cp.ComponentRowQuery{Table: "ComponentRow", Columns: []string{"Id"}, Equal: cp.ComponentRow{"Owner": "pack"}})
		return err
	}
	provider.Preflight = func(ctx context.Context, reader cp.ComponentRowReader, _ cp.ComponentManagedRequest) error {
		return read(ctx, reader)
	}
	provider.Apply = func(context.Context, cp.ComponentMutationStore, cp.ComponentManagedRequest) error {
		mutationCalls++
		return nil
	}
	provider.VerifyProjection = func(ctx context.Context, reader cp.ComponentRowReader, request cp.ComponentManagedRequest) ([]byte, []byte, error) {
		return request.ExpectedProjection, []byte(`{"live":true}`), read(ctx, reader)
	}
	provider.ListLiveBusinessKeys = func(ctx context.Context, reader cp.ComponentRowReader, _ cp.ComponentManagedSetRequest) ([]cp.ComponentLiveBusinessKey, error) {
		return []cp.ComponentLiveBusinessKey{{BusinessKey: "b", Customized: true}, {BusinessKey: "a"}}, read(ctx, reader)
	}
	provider.ReconcileSet = func(context.Context, cp.ComponentMutationStore, cp.ComponentManagedSetRequest, []string) error {
		mutationCalls++
		return nil
	}
	host, err := NewManagedComponentHost(db, catalog, managedHostRegistration(provider))
	if err != nil {
		t.Fatal(err)
	}
	query := regexp.QuoteMeta("SELECT `Id` FROM `ComponentRow` WHERE `Owner` = ?")
	mock.ExpectQuery(query).WithArgs("pack").WillReturnRows(sqlmock.NewRows([]string{"Id"}))
	request := managedHostRequest()
	if err := host.Preflight(context.Background(), request); err != nil {
		t.Fatal(err)
	}
	if err := host.Apply(context.Background(), request); err == nil || !strings.Contains(err.Error(), "legacy managed provider execution is disabled") {
		t.Fatalf("Apply() error = %v", err)
	}
	mock.ExpectQuery(query).WithArgs("pack").WillReturnRows(sqlmock.NewRows([]string{"Id"}))
	expected, live, err := host.VerifyProjection(context.Background(), request)
	if err != nil || string(expected) != `{"expected":true}` || string(live) != `{"live":true}` {
		t.Fatalf("VerifyProjection() = %s, %s, %v", expected, live, err)
	}
	mock.ExpectQuery(query).WithArgs("pack").WillReturnRows(sqlmock.NewRows([]string{"Id"}))
	setRequest := cp.ComponentManagedSetRequest{PackName: "pack", Kind: cp.ComponentKindReportType}
	liveKeys, err := host.ListLiveBusinessKeys(context.Background(), setRequest)
	if err != nil || len(liveKeys) != 2 || liveKeys[0].BusinessKey != "a" || liveKeys[1].BusinessKey != "b" || !liveKeys[1].Customized {
		t.Fatalf("ListLiveBusinessKeys() = %#v, %v", liveKeys, err)
	}
	if err := host.ReconcileSet(context.Background(), setRequest, []string{"b", "a"}); err == nil || !strings.Contains(err.Error(), "legacy managed provider execution is disabled") {
		t.Fatalf("ReconcileSet() error = %v", err)
	}
	if mutationCalls != 0 {
		t.Fatalf("deprecated mutation callbacks = %d, want 0", mutationCalls)
	}
	if catalog.calls["ComponentRow"] != 3 {
		t.Fatalf("column snapshots = %d, want one for every read callback", catalog.calls["ComponentRow"])
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestManagedComponentHostNeverInvokesDeprecatedMutationCallbacks(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = db.Close() })
	mutationCalls := 0
	provider := completeManagedComponentProvider()
	provider.Apply = func(context.Context, cp.ComponentMutationStore, cp.ComponentManagedRequest) error {
		mutationCalls++
		return nil
	}
	provider.ReconcileSet = func(context.Context, cp.ComponentMutationStore, cp.ComponentManagedSetRequest, []string) error {
		mutationCalls++
		return nil
	}
	host, err := NewManagedComponentHost(db, testComponentColumnCatalog(), managedHostRegistration(provider))
	if err != nil {
		t.Fatal(err)
	}
	for _, call := range []func() error{
		func() error { return host.Apply(context.Background(), managedHostRequest()) },
		func() error {
			return host.ReconcileSet(context.Background(), cp.ComponentManagedSetRequest{PackName: "pack", Kind: cp.ComponentKindReportType}, nil)
		},
	} {
		if err := call(); err == nil || !strings.Contains(err.Error(), "legacy managed provider execution is disabled") {
			t.Fatalf("mutation error = %v", err)
		}
	}
	if mutationCalls != 0 {
		t.Fatalf("deprecated mutation callbacks = %d, want 0", mutationCalls)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestManagedComponentHostRejectsNonCanonicalInputs(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = db.Close() })
	host, err := NewManagedComponentHost(db, testComponentColumnCatalog(), managedHostRegistration(completeManagedComponentProvider()))
	if err != nil {
		t.Fatal(err)
	}
	assertInvalid := func(name string, request cp.ComponentManagedRequest, want string) {
		t.Helper()
		if err := host.Preflight(context.Background(), request); err == nil || !strings.Contains(err.Error(), want) {
			t.Fatalf("%s error = %v, want %q", name, err, want)
		}
	}
	request := managedHostRequest()
	request.PackName = " pack"
	assertInvalid("pack", request, "not canonical")
	request = managedHostRequest()
	request.Component.BusinessKey = "component "
	assertInvalid("key", request, "not canonical")
	request = managedHostRequest()
	request.Component.Manageability = cp.ManageabilityDefaultsOnly
	assertInvalid("manageability", request, "does not support")
	request = managedHostRequest()
	request.Component.Payload = []byte(`{"other":true}`)
	assertInvalid("payload", request, "must match")
	request = managedHostRequest()
	request.ExpectedProjection = nil
	assertInvalid("projection", request, "projection is required")
	setRequest := cp.ComponentManagedSetRequest{PackName: "pack", Kind: cp.ComponentKindReportType}
	if err := host.ReconcileSet(context.Background(), setRequest, []string{"same", "same"}); err == nil || !strings.Contains(err.Error(), "duplicated") {
		t.Fatalf("duplicate set error = %v", err)
	}
	if err := host.ReconcileSet(context.Background(), setRequest, []string{" padded"}); err == nil || !strings.Contains(err.Error(), "not canonical") {
		t.Fatalf("padded set error = %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatal(err)
	}
}

func TestManagedComponentHostRejectsAmbiguousRegistration(t *testing.T) {
	db, _, err := sqlmock.New()
	if err != nil {
		t.Fatal(err)
	}
	t.Cleanup(func() { _ = db.Close() })
	registration := managedHostRegistration(completeManagedComponentProvider())
	registration.Contract.WriteSet = append(registration.Contract.WriteSet, cp.ComponentWriteSurface{Table: "componentrow", Fact: "logical component rows"})
	if _, err := NewManagedComponentHost(db, testComponentColumnCatalog(), registration); err == nil || !strings.Contains(err.Error(), "more than once") {
		t.Fatalf("duplicate table error = %v", err)
	}
	registration = managedHostRegistration(completeManagedComponentProvider())
	registration.Contract.Manageabilities = map[cp.Manageability]struct{}{cp.Manageability("custom"): {}}
	registration.Capability.Manageabilities = registration.Contract.Manageabilities
	if _, err := NewManagedComponentHost(db, testComponentColumnCatalog(), registration); err == nil || !strings.Contains(err.Error(), "inconsistent") {
		t.Fatalf("manageability mismatch error = %v", err)
	}
}
func managedHostRegistration(provider *cp.ComponentManagedProvider) cp.ComponentKindRegistration {
	allowed := map[cp.Manageability]struct{}{cp.ManageabilityLocked: {}}
	return cp.ComponentKindRegistration{
		Contract:        cp.ComponentKindContract{Kind: cp.ComponentKindReportType, WriteSet: testComponentWriteSet(), OwnerDiscriminator: cp.ComponentOwnerStrategyOwnerPack, Manageabilities: allowed},
		Capability:      cp.SourceBackedComponentCapability{Kind: cp.ComponentKindReportType, Installable: true, Manageabilities: allowed},
		ManagedProvider: provider,
	}
}

func completeManagedComponentProvider() *cp.ComponentManagedProvider {
	return &cp.ComponentManagedProvider{
		Preflight: func(context.Context, cp.ComponentRowReader, cp.ComponentManagedRequest) error { return nil },
		Apply:     func(context.Context, cp.ComponentMutationStore, cp.ComponentManagedRequest) error { return nil },
		VerifyProjection: func(context.Context, cp.ComponentRowReader, cp.ComponentManagedRequest) ([]byte, []byte, error) {
			return []byte(`{}`), []byte(`{}`), nil
		},
		ListLiveBusinessKeys: func(context.Context, cp.ComponentRowReader, cp.ComponentManagedSetRequest) ([]cp.ComponentLiveBusinessKey, error) {
			return nil, nil
		},
		ReconcileSet: func(context.Context, cp.ComponentMutationStore, cp.ComponentManagedSetRequest, []string) error {
			return nil
		},
	}
}

func managedHostRequest() cp.ComponentManagedRequest {
	payload := []byte(`{"payload":true}`)
	return cp.ComponentManagedRequest{
		PackName: "pack", Component: cp.ManifestComponent{Kind: cp.ComponentKindReportType, BusinessKey: "component", Manageability: cp.ManageabilityLocked},
		Payload: payload, ExpectedProjection: []byte(`{"expected":true}`),
	}
}
