package controlplane

import (
	"errors"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	database "github.com/nexuscrm/db"
	"github.com/nexuscrm/shared/pkg/constants"
)

func TestBackfillLookupReferencesSkipsRowsOutsideBootstrapOwnership(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("FROM _System_Field").
		WithArgs(constants.FieldTypeReference, constants.FieldTypeMasterDetail).
		WillReturnRows(sqlmock.NewRows([]string{"Id", "ObjectApiName", "ApiName", "Type"}).
			AddRow("fld_profile", "Case", "ProfileId", "reference").
			AddRow("fld_account", "Issue", "AccountId", "reference"))

	if err := BackfillLookupReferences(database.NewConnectionFromDB(db)); err != nil {
		t.Fatalf("BackfillLookupReferences returned err = %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestBackfillLookupReferencesQueriesOnlyActiveFieldsOnActiveObjects(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("(?s)FROM _System_Field f.*JOIN _System_Object o.*COALESCE\\(f\\.IsDeleted, 0\\)\\s*=\\s*0.*COALESCE\\(o\\.IsDeleted, 0\\)\\s*=\\s*0.*TRIM\\(f\\.ReferenceTo\\)").
		WithArgs(constants.FieldTypeReference, constants.FieldTypeMasterDetail).
		WillReturnRows(sqlmock.NewRows([]string{"Id", "ObjectApiName", "ApiName", "Type"}))

	if err := BackfillLookupReferences(database.NewConnectionFromDB(db)); err != nil {
		t.Fatalf("BackfillLookupReferences returned err = %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestBackfillLookupReferencesUsesPlatformReferenceTargets(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	mock.ExpectQuery("FROM _System_Field").
		WithArgs(constants.FieldTypeReference, constants.FieldTypeMasterDetail).
		WillReturnRows(sqlmock.NewRows([]string{"Id", "ObjectApiName", "ApiName", "Type"}).
			AddRow("fld_profile", constants.TableUser, "ProfileId", "reference").
			AddRow("fld_record_type", "AnyObject", constants.FieldRecordTypeID, "reference").
			AddRow("fld_owner", "AnyObject", constants.FieldOwnerID, "reference"))
	mock.ExpectExec("UPDATE _System_Field").
		WithArgs("[\"Profile\"]", "fld_profile").
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec("UPDATE _System_Field").
		WithArgs("[\"RecordType\"]", "fld_record_type").
		WillReturnResult(sqlmock.NewResult(0, 1))
	mock.ExpectExec("UPDATE _System_Field").
		WithArgs("[\"User\"]", "fld_owner").
		WillReturnResult(sqlmock.NewResult(0, 1))

	if err := BackfillLookupReferences(database.NewConnectionFromDB(db)); err != nil {
		t.Fatalf("BackfillLookupReferences returned err = %v", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}

func TestBackfillLookupReferencesFailsClosedOnOwnedUpdateError(t *testing.T) {
	db, mock, err := sqlmock.New()
	if err != nil {
		t.Fatalf("sqlmock: %v", err)
	}
	defer db.Close()

	updateErr := errors.New("write failed")
	mock.ExpectQuery("FROM _System_Field").
		WithArgs(constants.FieldTypeReference, constants.FieldTypeMasterDetail).
		WillReturnRows(sqlmock.NewRows([]string{"Id", "ObjectApiName", "ApiName", "Type"}).
			AddRow("fld_profile", constants.TableUser, "ProfileId", "reference"))
	mock.ExpectExec("UPDATE _System_Field").
		WithArgs("[\"Profile\"]", "fld_profile").
		WillReturnError(updateErr)

	err = BackfillLookupReferences(database.NewConnectionFromDB(db))
	if err == nil {
		t.Fatal("BackfillLookupReferences returned nil err on owned update failure")
	}
	if !strings.Contains(err.Error(), "User.ProfileId") {
		t.Fatalf("err = %v, want offending managed field name", err)
	}
	if !strings.Contains(err.Error(), "update reference_to") {
		t.Fatalf("err = %v, want update failure reason", err)
	}
	if err := mock.ExpectationsWereMet(); err != nil {
		t.Fatalf("sql expectations: %v", err)
	}
}
