package packs

import (
	"context"
	"database/sql"
	"database/sql/driver"
	"strings"
	"testing"

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

type crossPackFieldRegistrationCarrierFixture struct {
	pack          *SourceBackedPack
	registrations ComponentKindRegistrationSet
	row           []driver.Value
}

func TestObjectSpecComponentCapturesComponentKindRegistrationSet(t *testing.T) {
	fixture := newCrossPackFieldRegistrationCarrierFixture(t)
	for _, component := range fixture.pack.Components() {
		objectSpec, ok := component.(*sourceBackedObjectSpecComponent)
		if !ok {
			continue
		}
		if objectSpec.registrations == nil || !objectSpec.registrations.reserves(ComponentKind(cp.ComponentKindApprovalProcess)) {
			t.Fatal("object_spec component did not capture the configured approval_process registration")
		}
		return
	}
	t.Fatal("registered peer pack has no object_spec component")
}

func TestVerifyPackObjectPhysicalSchemaCarriesComponentKindRegistrationSetToPeerDeclaration(t *testing.T) {
	fixture := newCrossPackFieldRegistrationCarrierFixture(t)
	defs := []domainschema.TableDefinition{{TableName: "Issue", TableType: "standard_object", Category: "jira"}}
	assertCrossPackFieldRegistrationModes(t, fixture, func(mock sqlmock.Sqlmock) {
		expectVerifyPackObjectPhysicalSchemaBaseQueries(mock, []driver.Value{
			"Issue", "PeerField__c", "pack", "peer-pack",
		}, []driver.Value{
			"Issue", "PeerField__c", "varchar(255)", "YES", nil, nil,
		})
	}, func(db *sql.DB, registrations *ComponentKindRegistrationSet) error {
		return verifyPackObjectPhysicalSchemaWithContextAndAuthoredFieldsAndComponentKindRegistrationSet(
			context.Background(), db, "crm-pack", defs, ManageabilityLocked, objectSpecAuthoredFieldSet{}, registrations,
		)
	}, func(db *sql.DB) error {
		return verifyPackObjectPhysicalSchema(db, "crm-pack", defs, ManageabilityLocked)
	})
}

func TestVerifyOwnerlessObjectSpecPartialCompatibilityCarriesComponentKindRegistrationSetToPeerDeclaration(t *testing.T) {
	fixture := newCrossPackFieldRegistrationCarrierFixture(t)
	def := crossPackRetryCompatibilityDefinition()
	physicalTables := map[string]bool{"issue": true}
	tableRegistry := map[string]packTableRegistryRow{"issue": {tableType: "standard_object", category: "jira", managed: true}}
	objectRegistry := map[string]packObjectRegistryRow{"issue": {tableType: "standard_object"}}
	fieldRegistry := map[string]map[string]packFieldRegistryRow{
		"issue": {
			"id":           {fieldAPIName: "Id", ownerKind: "pack", ownerPack: "crm-pack"},
			"subject":      {fieldAPIName: "Subject", ownerKind: "pack", ownerPack: "crm-pack"},
			"peerfield__c": {fieldAPIName: "PeerField__c", ownerKind: "pack", ownerPack: "peer-pack"},
		},
	}
	assertCrossPackFieldRegistrationModes(t, fixture, nil, func(db *sql.DB, registrations *ComponentKindRegistrationSet) error {
		return verifyOwnerlessObjectSpecPartialCompatibilityWithComponentKindRegistrationSet(
			context.Background(), db, "crm-pack", def, physicalTables, nil,
			tableRegistry, objectRegistry, fieldRegistry, registrations,
		)
	}, func(db *sql.DB) error {
		return verifyOwnerlessObjectSpecPartialCompatibility(
			context.Background(), db, "crm-pack", def, physicalTables, nil,
			tableRegistry, objectRegistry, fieldRegistry,
		)
	})
}

func assertCrossPackFieldRegistrationModes(
	t *testing.T,
	fixture crossPackFieldRegistrationCarrierFixture,
	prepare func(sqlmock.Sqlmock),
	registeredRun func(*sql.DB, *ComponentKindRegistrationSet) error,
	legacyRun func(*sql.DB) error,
) {
	t.Helper()
	for _, registered := range []bool{true, false} {
		name := "registered"
		if !registered {
			name = "legacy"
		}
		t.Run(name, func(t *testing.T) {
			db, mock, err := sqlmock.New()
			if err != nil {
				t.Fatal(err)
			}
			defer db.Close()
			if prepare != nil {
				prepare(mock)
			}
			expectInstalledPackStatusLookup(mock, "peer-pack", []driver.Value{"active", 0})
			expectInstalledPackDeclarationLookup(mock, "peer-pack", fixture.row)

			if registered {
				err = registeredRun(db, &fixture.registrations)
				if err != nil {
					t.Fatalf("registered peer declaration: %v", err)
				}
			} else {
				err = legacyRun(db)
				if err == nil || !strings.Contains(err.Error(), "approval_process") {
					t.Fatalf("legacy peer declaration error = %v, want missing approval_process registration", err)
				}
			}
			if err := mock.ExpectationsWereMet(); err != nil {
				t.Fatal(err)
			}
		})
	}
}

func newCrossPackFieldRegistrationCarrierFixture(t *testing.T) crossPackFieldRegistrationCarrierFixture {
	t.Helper()
	root := writeCrossPackFieldTestSource(t, "peer-pack", "PeerField__c")
	manifest := validPackManifestV2(`[
    {"kind":"object_spec","businessKey":"issue","manageability":"locked","path":"objects/issue.json"},
    {"kind":"approval_process","businessKey":"Quote/Quote_Approval","manageability":"locked","path":"approvalProcesses/Quote_Approval.approvalProcess-meta.xml"}
  ]`, `"dependencies": [],`)
	writeFile(t, root, "pack.json", strings.Replace(manifest, `"name": "jira-like-sample"`, `"name": "peer-pack"`, 1))
	writeFile(t, root, "approvalProcesses/Quote_Approval.approvalProcess-meta.xml", "<ApprovalProcess/>")

	registration := testComponentKindRegistration(cp.ComponentKindApprovalProcess, "ProcessDefinition")
	registration.BuildHandler = func(buildContext cp.ComponentBuildContext, _ cp.ComponentBuildRequest) error {
		return buildContext.AddManagedComponent(cp.ComponentBuildManagedComponent{ExpectedProjection: []byte(`{"rows":[]}`)})
	}
	registrations, err := NewComponentKindRegistrationSet([]cp.ComponentKindRegistration{registration})
	if err != nil {
		t.Fatal(err)
	}
	pack, err := ResolveInstalledSourceBackedPackForInstalledRowAtSourceRevisionWithPackSourceOptionsAndComponentKindRegistrationSet(
		"peer-pack", AppPack, ManifestFormatVersion2, string(PackSourceKindLocalPath), root, "", DefaultPackSourceOptions(), registrations,
	)
	if err != nil {
		t.Fatal(err)
	}
	payloadHash, err := PackPayloadHash(pack)
	if err != nil {
		t.Fatal(err)
	}
	return crossPackFieldRegistrationCarrierFixture{
		pack:          pack,
		registrations: registrations,
		row: []driver.Value{
			"peer-pack", "app-pack", "active", 0, ManifestFormatVersion2,
			string(PackSourceKindLocalPath), root, "", payloadHash, payloadHash,
		},
	}
}
