package packs

import (
	"context"
	"database/sql"
	"encoding/json"
	"strings"
	"testing"

	"github.com/nexuscrm/shared/pkg/models"
)

func TestResolveObjectSpecValueOwnershipAllowsCustomizedDefaultsOnlyRows(t *testing.T) {
	if got := ResolveObjectSpecValueOwnership(ManageabilityDefaultsOnly, true); got != ObjectSpecValueLiveOverrideAllowed {
		t.Fatalf("ResolveObjectSpecValueOwnership(defaults_only, customized) = %v, want live override allowed", got)
	}
}

func TestResolveObjectSpecValueOwnershipRequiresExactForLockedRows(t *testing.T) {
	if got := ResolveObjectSpecValueOwnership(ManageabilityLocked, true); got != ObjectSpecValueExactRequired {
		t.Fatalf("ResolveObjectSpecValueOwnership(locked, customized) = %v, want exact required", got)
	}
}

func TestResolveDefaultChildRowsValueOwnershipRequiresExactWithoutColumnOverride(t *testing.T) {
	if got := ResolveDefaultChildRowsValueOwnership(ManageabilityDefaultsOnly, false); got != ObjectSpecValueExactRequired {
		t.Fatalf("ResolveDefaultChildRowsValueOwnership(defaults_only, false) = %v, want exact required", got)
	}
}

func TestResolveDefaultChildRowsValueOwnershipAllowsExplicitColumnOverride(t *testing.T) {
	if got := ResolveDefaultChildRowsValueOwnership(ManageabilityDefaultsOnly, true); got != ObjectSpecValueLiveOverrideAllowed {
		t.Fatalf("ResolveDefaultChildRowsValueOwnership(defaults_only, true) = %v, want live override allowed", got)
	}
}

func TestResolveChildCloneRulesValueOwnershipAllowsExplicitColumnOverride(t *testing.T) {
	if got := ResolveChildCloneRulesValueOwnership(ManageabilityDefaultsOnly, true); got != ObjectSpecValueLiveOverrideAllowed {
		t.Fatalf("ResolveChildCloneRulesValueOwnership(defaults_only, customized) = %v, want live override allowed", got)
	}
}

func TestResolveChildCloneRulesValueOwnershipRequiresExactForLockedRows(t *testing.T) {
	if got := ResolveChildCloneRulesValueOwnership(ManageabilityLocked, true); got != ObjectSpecValueExactRequired {
		t.Fatalf("ResolveChildCloneRulesValueOwnership(locked, customized) = %v, want exact required", got)
	}
}

func TestVerifyPackObjectDefaultChildRowsFailsClosedWhenOnlyObjectRowIsCustomized(t *testing.T) {
	specs := []ObjectSpec{{
		APIName: "Campaign",
		DefaultChildRows: []models.DefaultChildRow{{
			TargetObject:      "CampaignMemberStatus",
			TargetLookupField: "CampaignId",
			FieldValues:       models.SObject{"Label": "Sent", "SortOrder": 1},
		}},
	}}
	registry := map[string]packObjectRegistryRow{
		"campaign": {
			defaultChildRows: sql.NullString{
				String: `[{"targetObject":"CampaignMemberStatus","targetLookupField":"CampaignId","fieldValues":{"Label":"Responded","SortOrder":2}}]`,
				Valid:  true,
			},
			isCustomized:               true,
			defaultChildRowsCustomized: false,
		},
	}

	err := verifyPackObjectDefaultChildRowsAgainstRegistry("test-pack", specs, ManageabilityDefaultsOnly, registry)
	if err == nil || !strings.Contains(err.Error(), "DefaultChildRows") {
		t.Fatalf("verifyPackObjectDefaultChildRowsAgainstRegistry = %v, want DefaultChildRows drift failure", err)
	}
}

func TestVerifyPackObjectDefaultChildRowsAllowsExplicitColumnOverride(t *testing.T) {
	specs := []ObjectSpec{{
		APIName: "Campaign",
		DefaultChildRows: []models.DefaultChildRow{{
			TargetObject:      "CampaignMemberStatus",
			TargetLookupField: "CampaignId",
			FieldValues:       models.SObject{"Label": "Sent", "SortOrder": 1},
		}},
	}}
	registry := map[string]packObjectRegistryRow{
		"campaign": {
			defaultChildRows: sql.NullString{
				String: `[{"targetObject":"CampaignMemberStatus","targetLookupField":"CampaignId","fieldValues":{"Label":"Responded","SortOrder":2}}]`,
				Valid:  true,
			},
			isCustomized:               true,
			defaultChildRowsCustomized: true,
		},
	}

	if err := verifyPackObjectDefaultChildRowsAgainstRegistry("test-pack", specs, ManageabilityDefaultsOnly, registry); err != nil {
		t.Fatalf("verifyPackObjectDefaultChildRowsAgainstRegistry explicit override = %v, want nil", err)
	}
}

func TestVerifyPackObjectChildCloneRulesDetectsDriftWhenObjectRowCustomized(t *testing.T) {
	specs := []ObjectSpec{testChildCloneRulesSpec()}
	registry := map[string]packObjectRegistryRow{
		"contract": {
			childCloneRules: sql.NullString{
				String: `[{"parentLookupField":"AccountId","sourceObject":"Entitlement","sourceLookupField":"AccountId","targetObject":"Entitlement","targetLookupField":"ContractId","fieldMappings":{"Name":"Name"}}]`,
				Valid:  true,
			},
			childCloneRulesPackName:      "test-pack",
			childCloneRulesBusinessKey:   "objects",
			childCloneRulesManageability: string(ManageabilityDefaultsOnly),
			childCloneRulesPackValue: sql.NullString{
				String: string(mustCanonicalChildCloneRules(t, specs[0].ChildCloneRules)),
				Valid:  true,
			},
			isCustomized:              true,
			childCloneRulesCustomized: false,
		},
	}

	err := verifyPackObjectChildCloneRulesAgainstRegistry("test-pack", "objects", specs, ManageabilityDefaultsOnly, registry)
	if err == nil || !strings.Contains(err.Error(), "ChildCloneRules") {
		t.Fatalf("verifyPackObjectChildCloneRulesAgainstRegistry = %v, want ChildCloneRules drift failure", err)
	}
}

func TestVerifyPackObjectChildCloneRulesAllowsExplicitColumnOverride(t *testing.T) {
	specs := []ObjectSpec{testChildCloneRulesSpec()}
	registry := map[string]packObjectRegistryRow{
		"contract": {
			childCloneRules: sql.NullString{
				String: `[{"parentLookupField":"AccountId","sourceObject":"Entitlement","sourceLookupField":"AccountId","targetObject":"Entitlement","targetLookupField":"ContractId","fieldMappings":{"Name":"Name"}}]`,
				Valid:  true,
			},
			childCloneRulesPackName:      "test-pack",
			childCloneRulesBusinessKey:   "objects",
			childCloneRulesManageability: string(ManageabilityDefaultsOnly),
			childCloneRulesPackValue: sql.NullString{
				String: string(mustCanonicalChildCloneRules(t, testChildCloneRulesSpec().ChildCloneRules)),
				Valid:  true,
			},
			isCustomized:              true,
			childCloneRulesCustomized: true,
		},
	}

	if err := verifyPackObjectChildCloneRulesAgainstRegistry("test-pack", "objects", specs, ManageabilityDefaultsOnly, registry); err != nil {
		t.Fatalf("verifyPackObjectChildCloneRulesAgainstRegistry explicit override = %v, want nil", err)
	}
}

func TestVerifyPackObjectChildCloneRulesDefersLegacyDefaultsOnlyDriftWithoutSidecars(t *testing.T) {
	specs := []ObjectSpec{testChildCloneRulesSpec()}
	registry := map[string]packObjectRegistryRow{
		"contract": {
			childCloneRules: sql.NullString{
				String: `[{"parentLookupField":"AccountId","sourceObject":"Entitlement","sourceLookupField":"AccountId","targetObject":"Entitlement","targetLookupField":"ContractId","fieldMappings":{"Name":"Name"}}]`,
				Valid:  true,
			},
		},
	}

	if err := verifyPackObjectChildCloneRulesAgainstRegistry("test-pack", "objects", specs, ManageabilityDefaultsOnly, registry); err != nil {
		t.Fatalf("verifyPackObjectChildCloneRulesAgainstRegistry legacy drift without sidecars = %v, want nil", err)
	}
}

func TestVerifyPackObjectChildCloneRulesFailsClosedForRemovedDefaultsOnlyRulesWithoutSidecars(t *testing.T) {
	specs := []ObjectSpec{{APIName: "Contract"}}
	registry := map[string]packObjectRegistryRow{
		"contract": {
			childCloneRules: sql.NullString{
				String: string(mustCanonicalChildCloneRules(t, testChildCloneRulesSpec().ChildCloneRules)),
				Valid:  true,
			},
		},
	}

	err := verifyPackObjectChildCloneRulesAgainstRegistry("test-pack", "objects", specs, ManageabilityDefaultsOnly, registry)
	if err == nil || !strings.Contains(err.Error(), "removed defaults_only rules") || !strings.Contains(err.Error(), "want []") {
		t.Fatalf("verifyPackObjectChildCloneRulesAgainstRegistry = %v, want removed defaults_only drift failure", err)
	}
}

func TestVerifyPackObjectChildCloneRulesFailsClosedForLockedDriftWithoutSidecars(t *testing.T) {
	specs := []ObjectSpec{testChildCloneRulesSpec()}
	registry := map[string]packObjectRegistryRow{
		"contract": {
			childCloneRules: sql.NullString{
				String: `[{"parentLookupField":"AccountId","sourceObject":"Entitlement","sourceLookupField":"AccountId","targetObject":"Entitlement","targetLookupField":"ContractId","fieldMappings":{"Name":"Name"}}]`,
				Valid:  true,
			},
		},
	}

	err := verifyPackObjectChildCloneRulesAgainstRegistry("test-pack", "objects", specs, ManageabilityLocked, registry)
	if err == nil || !strings.Contains(err.Error(), "ChildCloneRules") {
		t.Fatalf("verifyPackObjectChildCloneRulesAgainstRegistry = %v, want ChildCloneRules drift failure", err)
	}
}

func TestVerifyPackObjectChildCloneRulesAllowsLegacyMissingSidecarWhenLiveValueMatches(t *testing.T) {
	spec := testChildCloneRulesSpec()
	specs := []ObjectSpec{spec}
	registry := map[string]packObjectRegistryRow{
		"contract": {
			childCloneRules: sql.NullString{
				String: string(mustCanonicalChildCloneRules(t, spec.ChildCloneRules)),
				Valid:  true,
			},
		},
	}

	if err := verifyPackObjectChildCloneRulesAgainstRegistry("test-pack", "objects", specs, ManageabilityDefaultsOnly, registry); err != nil {
		t.Fatalf("verifyPackObjectChildCloneRulesAgainstRegistry legacy missing sidecar = %v, want nil", err)
	}
}

func TestVerifyPackObjectChildCloneRulesRequiresAllSidecarsOnceComponentHydrated(t *testing.T) {
	contract := testChildCloneRulesSpec()
	account := ObjectSpec{
		APIName: "Account",
		ChildCloneRules: []models.ChildCloneRule{{
			ParentLookupField: "AccountId",
			SourceObject:      "Contact",
			SourceLookupField: "AccountId",
			TargetObject:      "Contact",
			TargetLookupField: "AccountId",
			FieldMappings:     map[string]string{"LastName": "LastName"},
		}},
	}
	specs := []ObjectSpec{contract, account}
	registry := map[string]packObjectRegistryRow{
		"contract": {
			childCloneRules: sql.NullString{
				String: string(mustCanonicalChildCloneRules(t, contract.ChildCloneRules)),
				Valid:  true,
			},
			childCloneRulesPackName:      "test-pack",
			childCloneRulesBusinessKey:   "objects",
			childCloneRulesManageability: string(ManageabilityDefaultsOnly),
			childCloneRulesPackValue: sql.NullString{
				String: string(mustCanonicalChildCloneRules(t, contract.ChildCloneRules)),
				Valid:  true,
			},
		},
		"account": {
			childCloneRules: sql.NullString{
				String: string(mustCanonicalChildCloneRules(t, account.ChildCloneRules)),
				Valid:  true,
			},
		},
	}

	err := verifyPackObjectChildCloneRulesAgainstRegistry("test-pack", "objects", specs, ManageabilityDefaultsOnly, registry)
	if err == nil || !strings.Contains(err.Error(), "Account ChildCloneRulesPackName") || !strings.Contains(err.Error(), "Account ChildCloneRulesPackValue") {
		t.Fatalf("verifyPackObjectChildCloneRulesAgainstRegistry = %v, want missing Account sidecar failure", err)
	}
}

func TestVerifyPackObjectChildCloneRulesDetectsStaleSidecarOnOverride(t *testing.T) {
	specs := []ObjectSpec{testChildCloneRulesSpec()}
	registry := map[string]packObjectRegistryRow{
		"contract": {
			childCloneRules: sql.NullString{
				String: `[{"parentLookupField":"AccountId","sourceObject":"Entitlement","sourceLookupField":"AccountId","targetObject":"Entitlement","targetLookupField":"ContractId","fieldMappings":{"Name":"Name"}}]`,
				Valid:  true,
			},
			childCloneRulesPackName:      "test-pack",
			childCloneRulesBusinessKey:   "old-objects",
			childCloneRulesManageability: string(ManageabilityDefaultsOnly),
			childCloneRulesPackValue: sql.NullString{
				String: `[]`,
				Valid:  true,
			},
			childCloneRulesCustomized: true,
		},
	}

	err := verifyPackObjectChildCloneRulesAgainstRegistry("test-pack", "objects", specs, ManageabilityDefaultsOnly, registry)
	if err == nil || !strings.Contains(err.Error(), "ChildCloneRulesPackBusinessKey") || !strings.Contains(err.Error(), "ChildCloneRulesPackValue") {
		t.Fatalf("verifyPackObjectChildCloneRulesAgainstRegistry = %v, want sidecar drift failure", err)
	}
}

func TestCanonicalChildCloneRulesIgnoresFullRuleOrder(t *testing.T) {
	first := models.ChildCloneRule{
		ParentLookupField: "AccountId",
		SourceObject:      "Asset",
		SourceLookupField: "AccountId",
		TargetObject:      "Asset",
		TargetLookupField: "ContractId",
		FieldMappings:     map[string]string{"Name": "Name"},
	}
	second := models.ChildCloneRule{
		ParentLookupField: "OpportunityId",
		SourceObject:      "Asset",
		SourceLookupField: "OpportunityId",
		SourceIdField:     "SourceAssetId",
		HydrateSource:     true,
		TargetObject:      "Asset",
		TargetLookupField: "RenewalContractId",
		FieldMappings:     map[string]string{"Name": "Name"},
	}
	left, err := canonicalChildCloneRules([]models.ChildCloneRule{first, second})
	if err != nil {
		t.Fatalf("canonicalChildCloneRules left: %v", err)
	}
	right, err := canonicalChildCloneRules([]models.ChildCloneRule{second, first})
	if err != nil {
		t.Fatalf("canonicalChildCloneRules right: %v", err)
	}
	if string(left) != string(right) {
		t.Fatalf("canonicalChildCloneRules order mismatch:\nleft=%s\nright=%s", left, right)
	}
}

func TestValidateChildCloneRulesForSpecsRejectsBadSamePackReferenceBeforeApply(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName:      "Parent__c",
			TableType:    "custom_object",
			SharingModel: "Private",
			ChildCloneRules: []models.ChildCloneRule{{
				ParentLookupField: "SourceParent__c",
				SourceObject:      "Source__c",
				SourceLookupField: "SourceParent__c",
				TargetObject:      "Target__c",
				TargetLookupField: "MissingParent__c",
				FieldMappings:     map[string]string{"Name": "Name"},
			}},
			Fields: []FieldSpec{
				{APIName: "Name", Label: "Name", Type: "string", Required: true, IsNameField: true},
				{APIName: "SourceParent__c", Label: "Source Parent", Type: "reference", ReferenceTo: []string{"Account"}},
			},
		},
		{
			APIName:      "Source__c",
			TableType:    "custom_object",
			SharingModel: "Private",
			Fields: []FieldSpec{
				{APIName: "Name", Label: "Name", Type: "string", Required: true, IsNameField: true},
				{APIName: "SourceParent__c", Label: "Source Parent", Type: "reference", ReferenceTo: []string{"Account"}},
			},
		},
		{
			APIName:      "Target__c",
			TableType:    "custom_object",
			SharingModel: "Private",
			Fields: []FieldSpec{
				{APIName: "Name", Label: "Name", Type: "string", Required: true, IsNameField: true},
			},
		},
	}

	err := ValidateChildCloneRulesForSpecs(context.Background(), specs, nil, true)
	if err == nil || !strings.Contains(err.Error(), "targetLookupField") {
		t.Fatalf("ValidateChildCloneRulesForSpecs = %v, want targetLookupField preflight failure", err)
	}
}

func TestValidateChildCloneRulesForSpecsWithSnapshotAllowsCrossComponentReferencesAtApply(t *testing.T) {
	parentSpec := ObjectSpec{
		APIName:      "Parent__c",
		TableType:    "custom_object",
		SharingModel: "Private",
		ChildCloneRules: []models.ChildCloneRule{{
			ParentLookupField: "Account__c",
			SourceObject:      "Source__c",
			SourceLookupField: "Account__c",
			TargetObject:      "Child__c",
			TargetLookupField: "Parent__c",
			FieldMappings:     map[string]string{"Name": "Name"},
		}},
		Fields: []FieldSpec{
			{APIName: "Name", Label: "Name", Type: "string", Required: true, IsNameField: true},
			{APIName: "Account__c", Label: "Account", Type: "reference", ReferenceTo: []string{"Account"}},
		},
	}
	sourceSpec := ObjectSpec{
		APIName:      "Source__c",
		TableType:    "custom_object",
		SharingModel: "Private",
		Fields: []FieldSpec{
			{APIName: "Name", Label: "Name", Type: "string", Required: true, IsNameField: true},
			{APIName: "Account__c", Label: "Account", Type: "reference", ReferenceTo: []string{"Account"}},
		},
	}
	childSpec := ObjectSpec{
		APIName:      "Child__c",
		TableType:    "custom_object",
		SharingModel: "Private",
		Fields: []FieldSpec{
			{APIName: "Name", Label: "Name", Type: "string", Required: true, IsNameField: true},
			{APIName: "Parent__c", Label: "Parent", Type: "reference", ReferenceTo: []string{"Parent__c"}},
		},
	}

	err := ValidateChildCloneRulesForSpecsWithSnapshot(context.Background(), []ObjectSpec{parentSpec}, nil, nil, false)
	if err == nil || !strings.Contains(err.Error(), "sourceObject") || !strings.Contains(err.Error(), "targetObject") {
		t.Fatalf("ValidateChildCloneRulesForSpecsWithSnapshot current-component only = %v, want unresolved cross-component references", err)
	}

	err = ValidateChildCloneRulesForSpecsWithSnapshot(
		context.Background(),
		[]ObjectSpec{parentSpec},
		[]ObjectSpec{parentSpec, sourceSpec, childSpec},
		nil,
		false,
	)
	if err != nil {
		t.Fatalf("ValidateChildCloneRulesForSpecsWithSnapshot pack-wide = %v, want nil", err)
	}
}

func TestCanonicalDefaultChildRowsIgnoresRowOrder(t *testing.T) {
	left, err := canonicalDefaultChildRows([]models.DefaultChildRow{
		{TargetObject: "Child__c", TargetLookupField: "Parent__c", FieldValues: models.SObject{"Label": "A", "SortOrder": 1}},
		{TargetObject: "Child__c", TargetLookupField: "Parent__c", FieldValues: models.SObject{"Label": "B", "SortOrder": 2}},
	})
	if err != nil {
		t.Fatalf("canonicalDefaultChildRows left: %v", err)
	}
	right, err := canonicalDefaultChildRows([]models.DefaultChildRow{
		{TargetObject: "Child__c", TargetLookupField: "Parent__c", FieldValues: models.SObject{"SortOrder": 2, "Label": "B"}},
		{TargetObject: "Child__c", TargetLookupField: "Parent__c", FieldValues: models.SObject{"SortOrder": 1, "Label": "A"}},
	})
	if err != nil {
		t.Fatalf("canonicalDefaultChildRows right: %v", err)
	}
	if string(left) != string(right) {
		var prettyLeft, prettyRight any
		_ = json.Unmarshal(left, &prettyLeft)
		_ = json.Unmarshal(right, &prettyRight)
		t.Fatalf("canonicalDefaultChildRows mismatch:\nleft:  %v\nright: %v", prettyLeft, prettyRight)
	}
}

func testChildCloneRulesSpec() ObjectSpec {
	return ObjectSpec{
		APIName: "Contract",
		ChildCloneRules: []models.ChildCloneRule{{
			ParentLookupField: "AccountId",
			SourceObject:      "Asset",
			SourceLookupField: "AccountId",
			TargetObject:      "Asset",
			TargetLookupField: "ContractId",
			FieldMappings:     map[string]string{"Name": "Name"},
		}},
	}
}

func mustCanonicalChildCloneRules(t *testing.T, rules []models.ChildCloneRule) []byte {
	t.Helper()
	out, err := canonicalChildCloneRules(rules)
	if err != nil {
		t.Fatalf("canonicalChildCloneRules: %v", err)
	}
	return out
}
