package catalogconsumer

import (
	"context"
	"errors"
	"testing"
	"time"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/nexuscrm/metadata/catalogsnapshot"
	consumerv1 "github.com/pingcap-inc/nexuscrm-contract/pkg/catalog/consumerv1"
	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	"github.com/pingcap-inc/nexuscrm-controlplane/catalogapp"
)

func TestConsumerRejectsIncompleteOrMixedActivatedSelection(t *testing.T) {
	for _, name := range []string{
		"missing selection", "duplicate selection", "malformed digest", "missing artifact",
		"corrupt artifact", "wrong artifact digest", "wrong org", "older snapshot", "newer snapshot",
		"zero operation epoch", "security behind operation", "org revision behind installation",
		"selection row error",
	} {
		t.Run(name, func(t *testing.T) {
			db, mock, err := sqlmock.New()
			if err != nil {
				t.Fatal(err)
			}
			defer db.Close()
			store := testSnapshotStore(t)
			selection := testSelection(t, 7)
			digest := selection.CatalogSnapshotDigest
			operationEpoch, securityEpoch, orgRevision := "17", "17", "8"
			switch name {
			case "malformed digest":
				digest = "invalid"
			case "missing artifact":
				delete(store, catalogsnapshot.Digest(digest))
			case "corrupt artifact":
				store[catalogsnapshot.Digest(digest)] = catalogsnapshot.Artifact{}
			case "wrong artifact digest":
				store[catalogsnapshot.Digest(digest)] = testSnapshot(t, testOrgID, 8)
			case "wrong org", "older snapshot", "newer snapshot":
				org, revision := testOrgID, uint64(7)
				if name == "wrong org" {
					org = "other-org"
				}
				if name == "older snapshot" {
					revision = 6
				}
				if name == "newer snapshot" {
					revision = 8
				}
				artifact := testSnapshot(t, org, revision)
				digest = string(artifact.Digest())
				store[artifact.Digest()] = artifact
			case "zero operation epoch":
				operationEpoch = "0"
			case "security behind operation":
				securityEpoch = "16"
			case "org revision behind installation":
				orgRevision = "6"
			}
			reader, err := New(db, store)
			if err != nil {
				t.Fatal(err)
			}
			scope, err := NewAuthorizedConsumerScope(testOrgID, testInstallationID, testOtherWorkload, []string{consumerv1.AppMetadataCapabilityName})
			if err != nil {
				t.Fatal(err)
			}
			mock.ExpectBegin()
			expectActiveInstallation(mock, 7, testReleaseDigest, []string{testOtherWorkload}, []string{"runtime.records.query.v1"})
			expectConsumerGrantAuthority(mock, 7, []catalogapp.ConsumerGrant{{AuthorizedWorkloadID: testOtherWorkload, CapabilityID: consumerv1.AppMetadataCapabilityName}})
			rows := sqlmock.NewRows([]string{"EvidenceDigest", "ExpectedSecurityEpoch", "CatalogRevision", "LastSecurityEpoch"})
			if name != "missing selection" {
				rows.AddRow(digest, []byte(operationEpoch), []byte(orgRevision), []byte(securityEpoch))
			}
			if name == "duplicate selection" || name == "selection row error" {
				rows.AddRow(digest, []byte(operationEpoch), []byte(orgRevision), []byte(securityEpoch))
			}
			if name == "selection row error" {
				rows.RowError(1, errors.New("interrupted read"))
			}
			mock.ExpectQuery("(?s)SELECT.*FROM _System_CatalogAppOperationJournal").WithArgs(testOperationID).WillReturnRows(rows)
			mock.ExpectRollback()
			ctx, cancel := context.WithTimeout(context.Background(), time.Second)
			defer cancel()
			_, err = reader.GetAppMetadataView(ctx, scope, consumerv1.GetRequest{})
			var failure *consumerv1.Failure
			if !errors.As(err, &failure) || failure.Category != common.FailureInternal {
				t.Fatalf("invalid selected authority was served: %v", err)
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatal(err)
			}
		})
	}
}
