package packs

import (
	"errors"
	"reflect"
	"strings"
	"testing"
)

func TestChildRelFKDeclsFromObjectSpecsExpandsReferenceTargets(t *testing.T) {
	decls := childRelFKDeclsFromObjectSpecs([]ObjectSpec{
		{
			APIName: "Task",
			Fields: []FieldSpec{
				{APIName: "WhoId", Type: "reference", ReferenceTo: []string{"Contact", "Lead"}, ChildRelationshipName: "Tasks"},
				{APIName: "ParentId", Type: "masterdetail", ReferenceTo: []string{"Account"}, ChildRelationshipName: "ChildTasks"},
				{APIName: "LookupId", Type: "lookup", ReferenceTo: []string{"Case"}, ChildRelationshipName: "LookupTasks"},
				{APIName: "Name", Type: "string", ChildRelationshipName: "Ignored"},
				{APIName: "OrphanId", Type: "reference", ChildRelationshipName: "NoParents"},
			},
		},
	})
	want := []ChildRelFKDecl{
		{ObjectAPIName: "Task", FieldAPIName: "WhoId", ParentAPIName: "Contact", ChildRelationshipName: "Tasks"},
		{ObjectAPIName: "Task", FieldAPIName: "WhoId", ParentAPIName: "Lead", ChildRelationshipName: "Tasks"},
		{ObjectAPIName: "Task", FieldAPIName: "ParentId", ParentAPIName: "Account", ChildRelationshipName: "ChildTasks"},
		{ObjectAPIName: "Task", FieldAPIName: "LookupId", ParentAPIName: "Case", ChildRelationshipName: "LookupTasks"},
	}
	if !reflect.DeepEqual(decls, want) {
		t.Fatalf("decls = %#v, want %#v", decls, want)
	}
}

func TestValidateChildRelationshipNamesPresent_AllPopulated(t *testing.T) {
	decls := []ChildRelFKDecl{
		{ObjectAPIName: "Contact", FieldAPIName: "AccountId", ParentAPIName: "Account", ChildRelationshipName: "Contacts"},
		{ObjectAPIName: "Opportunity", FieldAPIName: "AccountId", ParentAPIName: "Account", ChildRelationshipName: "Opportunities"},
	}
	if err := ValidateChildRelationshipNamesPresent(decls); err != nil {
		t.Fatalf("expected nil; got %v", err)
	}
}

// F7: empty / whitespace-only / missing-key childRelationshipName is rejected.
func TestValidateChildRelationshipNamesPresent_RejectsBlanks(t *testing.T) {
	decls := []ChildRelFKDecl{
		{ObjectAPIName: "Contact", FieldAPIName: "AccountId", ParentAPIName: "Account", ChildRelationshipName: "Contacts"},
		{ObjectAPIName: "Opportunity", FieldAPIName: "AccountId", ParentAPIName: "Account", ChildRelationshipName: ""},
		{ObjectAPIName: "Case", FieldAPIName: "AccountId", ParentAPIName: "Account", ChildRelationshipName: "   \t"},
	}
	err := ValidateChildRelationshipNamesPresent(decls)
	if err == nil {
		t.Fatalf("expected error for blank childRelationshipName, got nil")
	}
	if !errors.Is(err, ErrChildRelNameMissing) {
		t.Errorf("err = %v; want ErrChildRelNameMissing", err)
	}
	// stable order: Case before Opportunity
	if !strings.Contains(err.Error(), "Case.AccountId") || !strings.Contains(err.Error(), "Opportunity.AccountId") {
		t.Errorf("err message must list both offending decls: %v", err)
	}
	if strings.Index(err.Error(), "Case.AccountId") > strings.Index(err.Error(), "Opportunity.AccountId") {
		t.Errorf("err diagnostics must be in stable (object, field) order: %v", err)
	}
}

func TestValidateChildRelationshipNameSyntaxRejectsUnsafeIdentifiers(t *testing.T) {
	decls := []ChildRelFKDecl{
		{ObjectAPIName: "Task", FieldAPIName: "WhatId", ParentAPIName: "Account", ChildRelationshipName: "Bad Name"},
		{ObjectAPIName: "Task", FieldAPIName: "WhoId", ParentAPIName: "Contact", ChildRelationshipName: "123Children"},
		{ObjectAPIName: "Case", FieldAPIName: "AccountId", ParentAPIName: "Account", ChildRelationshipName: "Children-Rel"},
		{ObjectAPIName: "Case", FieldAPIName: "ParentId", ParentAPIName: "Case", ChildRelationshipName: "Select"},
		{ObjectAPIName: "Case", FieldAPIName: "ContactId", ParentAPIName: "Contact", ChildRelationshipName: " Contacts "},
		{ObjectAPIName: "Event", FieldAPIName: "WhatId", ParentAPIName: "Account", ChildRelationshipName: "Using"},
		{ObjectAPIName: "Event", FieldAPIName: "WhoId", ParentAPIName: "Contact", ChildRelationshipName: "First"},
		{ObjectAPIName: "Task", FieldAPIName: "OwnerId", ParentAPIName: "User", ChildRelationshipName: "Last"},
		{ObjectAPIName: "Task", FieldAPIName: "AccountId", ParentAPIName: "Account", ChildRelationshipName: "Nulls"},
		{ObjectAPIName: "Task", FieldAPIName: "ParentId", ParentAPIName: "Case", ChildRelationshipName: "Min"},
	}
	err := ValidateChildRelationshipNameSyntax(decls)
	if !errors.Is(err, ErrChildRelNameInvalid) {
		t.Fatalf("err = %v, want ErrChildRelNameInvalid", err)
	}
	for _, want := range []string{"Bad Name", "123Children", "Children-Rel", "Select", "Using", "First", "Last", "Nulls", "Min", "SOQL reserved word", " Contacts ", "leading or trailing whitespace"} {
		if !strings.Contains(err.Error(), want) {
			t.Fatalf("err = %v, want %q", err, want)
		}
	}
}

func TestValidateChildRelationshipNameParentFieldCollisionsRejectsParentFields(t *testing.T) {
	specs := []ObjectSpec{
		{
			APIName: "Account",
			Fields: []FieldSpec{
				{APIName: "Name", Type: "string"},
				{APIName: "Contacts", Type: "string"},
			},
		},
		{
			APIName: "Contact",
			Fields: []FieldSpec{
				{APIName: "AccountId", Type: "reference", ReferenceTo: []string{"Account"}, ChildRelationshipName: "Contacts"},
			},
		},
		{
			APIName: "Case",
			Fields: []FieldSpec{
				{APIName: "AccountId", Type: "reference", ReferenceTo: []string{"Account"}, ChildRelationshipName: "OwnerId"},
			},
		},
	}
	decls := ChildRelFKDeclsFromObjectSpecs(specs)

	err := ValidateChildRelationshipNameParentFieldCollisions(decls, specs)
	if !errors.Is(err, ErrChildRelNameInvalid) {
		t.Fatalf("err = %v, want ErrChildRelNameInvalid", err)
	}
	for _, want := range []string{"Contact.AccountId", "Contacts", "Account.Contacts", "Case.AccountId", "OwnerId", "Account.OwnerId"} {
		if !strings.Contains(err.Error(), want) {
			t.Fatalf("err = %v, want %q", err, want)
		}
	}
}

func TestValidateChildRelationshipUniqueness_Unique(t *testing.T) {
	decls := []ChildRelFKDecl{
		{ObjectAPIName: "Contact", FieldAPIName: "AccountId", ParentAPIName: "Account", ChildRelationshipName: "Contacts"},
		{ObjectAPIName: "Opportunity", FieldAPIName: "AccountId", ParentAPIName: "Account", ChildRelationshipName: "Opportunities"},
		// same name across different parents is fine
		{ObjectAPIName: "Contact", FieldAPIName: "ReportsToId", ParentAPIName: "Contact", ChildRelationshipName: "DirectReports"},
	}
	if err := ValidateChildRelationshipUniqueness(decls); err != nil {
		t.Fatalf("expected nil; got %v", err)
	}
}

// Refinement 3: per-parent duplicate childRelationshipName is rejected
// (case-insensitive on both parent and name).
func TestValidateChildRelationshipUniqueness_RejectsCaseInsensitiveDuplicates(t *testing.T) {
	decls := []ChildRelFKDecl{
		{ObjectAPIName: "MyObject1__c", FieldAPIName: "AccountId", ParentAPIName: "Account", ChildRelationshipName: "Things"},
		{ObjectAPIName: "MyObject2__c", FieldAPIName: "AcctId", ParentAPIName: "Account", ChildRelationshipName: "things"},
	}
	err := ValidateChildRelationshipUniqueness(decls)
	if err == nil {
		t.Fatalf("expected error for duplicate childRelationshipName, got nil")
	}
	if !errors.Is(err, ErrChildRelNameDuplicate) {
		t.Errorf("err = %v; want ErrChildRelNameDuplicate", err)
	}
	if !strings.Contains(err.Error(), "MyObject1__c.AccountId") || !strings.Contains(err.Error(), "MyObject2__c.AcctId") {
		t.Errorf("err must list both colliders: %v", err)
	}
}

func TestValidateChildRelationshipUniqueness_BlanksAreSkippedHere(t *testing.T) {
	// Blank-presence rejection is the responsibility of
	// ValidateChildRelationshipNamesPresent (F7); the uniqueness check
	// must not double-report or treat blank strings as a collision class.
	decls := []ChildRelFKDecl{
		{ObjectAPIName: "X", FieldAPIName: "AId", ParentAPIName: "Account", ChildRelationshipName: ""},
		{ObjectAPIName: "Y", FieldAPIName: "AId", ParentAPIName: "Account", ChildRelationshipName: "   "},
	}
	if err := ValidateChildRelationshipUniqueness(decls); err != nil {
		t.Fatalf("blank rows must not collide; got %v", err)
	}
}

// F2: install-pack live-collision check rejects a proposal whose
// (parent, name) tuple already lives on a different (pack, object, field).
func TestCheckInstallPackChildRelationshipCollisions_RejectsCrossPack(t *testing.T) {
	live := []LiveChildRelFKRow{
		{
			SourcePack:            "salesforce-crm-standard",
			ObjectAPIName:         "Contact",
			FieldAPIName:          "AccountId",
			ParentAPIName:         "Account",
			ChildRelationshipName: "Contacts",
		},
	}
	proposed := []ChildRelFKDecl{
		{
			ObjectAPIName:         "MyContact__c",
			FieldAPIName:          "AcctId",
			ParentAPIName:         "Account",
			ChildRelationshipName: "Contacts",
		},
	}
	err := CheckInstallPackChildRelationshipCollisions("rogue-pack", proposed, live)
	if err == nil {
		t.Fatalf("expected fail-closed collision error; got nil")
	}
	if !errors.Is(err, ErrChildRelNameDuplicate) {
		t.Errorf("err = %v; want ErrChildRelNameDuplicate", err)
	}
}

// F6: a pack rewriting its own (object, field) row with the same child
// relationship name is idempotent re-apply, NOT a collision.
func TestCheckInstallPackChildRelationshipCollisions_F6_SelfRewriteAllowed(t *testing.T) {
	live := []LiveChildRelFKRow{
		{
			SourcePack:            "salesforce-crm-standard",
			ObjectAPIName:         "Contact",
			FieldAPIName:          "AccountId",
			ParentAPIName:         "Account",
			ChildRelationshipName: "Contacts",
		},
	}
	proposed := []ChildRelFKDecl{
		{
			ObjectAPIName:         "Contact",
			FieldAPIName:          "AccountId",
			ParentAPIName:         "Account",
			ChildRelationshipName: "Contacts",
		},
	}
	if err := CheckInstallPackChildRelationshipCollisions("salesforce-crm-standard", proposed, live); err != nil {
		t.Fatalf("self-rewrite must be allowed (F6); got %v", err)
	}
}

func TestCheckInstallPackChildRelationshipCollisions_F6_AnchorIsPackObjectField_NotPK(t *testing.T) {
	// Same (proposedPack, object, field) but conceptually different live
	// rows (e.g. uninstall+reinstall regenerated row IDs) — F6 anchors on
	// (source_pack, object, field_api_name), not on row PK or timestamp.
	live := []LiveChildRelFKRow{
		{
			SourcePack:            "salesforce-crm-standard",
			ObjectAPIName:         "Contact",
			FieldAPIName:          "AccountId",
			ParentAPIName:         "Account",
			ChildRelationshipName: "Contacts",
		},
	}
	proposed := []ChildRelFKDecl{
		{
			ObjectAPIName:         "contact", // case-insensitive match
			FieldAPIName:          "ACCOUNTID",
			ParentAPIName:         "Account",
			ChildRelationshipName: "Contacts",
		},
	}
	if err := CheckInstallPackChildRelationshipCollisions("Salesforce-CRM-Standard", proposed, live); err != nil {
		t.Fatalf("self-rewrite must be allowed across case differences; got %v", err)
	}
}

func TestCheckInstallPackChildRelationshipCollisions_DifferentParentNoCollision(t *testing.T) {
	live := []LiveChildRelFKRow{
		{
			SourcePack:            "salesforce-crm-standard",
			ObjectAPIName:         "Contact",
			FieldAPIName:          "AccountId",
			ParentAPIName:         "Account",
			ChildRelationshipName: "Contacts",
		},
	}
	proposed := []ChildRelFKDecl{
		// "Contacts" but for a different parent — not in same namespace.
		{
			ObjectAPIName:         "OtherContact__c",
			FieldAPIName:          "OpportunityId",
			ParentAPIName:         "Opportunity",
			ChildRelationshipName: "Contacts",
		},
	}
	if err := CheckInstallPackChildRelationshipCollisions("rogue-pack", proposed, live); err != nil {
		t.Fatalf("different parent must not collide; got %v", err)
	}
}

func TestCheckInstallPackChildRelationshipCollisions_EmptyInputsAreNoOp(t *testing.T) {
	if err := CheckInstallPackChildRelationshipCollisions("", nil, nil); err != nil {
		t.Fatalf("empty inputs must not error; got %v", err)
	}
}
