package automationintegration_test

import (
	"context"
	"database/sql"
	"testing"
	"time"

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

func TestPreviewBeforeUpdate_LeadStrictSchemaParityWithUpdate(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-349 preview/update parity test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	leadRecord, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue349",
		"LastName":  "PreviewParity",
		"Company":   "Issue349 Preview Parity Co",
		"Status":    "Open - Not Contacted",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Lead: %v", err)
	}
	leadID, _ := leadRecord["Id"].(string)
	if leadID == "" {
		t.Fatal("insert Lead returned empty Id")
	}

	badUpdates := models.SObject{
		"Status": map[string]interface{}{"bad": "value"},
	}

	updateErr := sm.Persistence.Update(ctx, "Lead", leadID, badUpdates, systemAdminSession())
	if updateErr == nil {
		t.Fatal("Update() error = nil, want strict schema rejection")
	}
	_, previewErr := sm.Persistence.PreviewBeforeUpdate(ctx, "Lead", leadID, badUpdates, nil, systemAdminSession())
	if previewErr == nil {
		t.Fatalf("PreviewBeforeUpdate() error = nil, want same strict schema rejection as Update() (%v)", updateErr)
	}
	if previewErr.Error() != updateErr.Error() {
		t.Fatalf("PreviewBeforeUpdate() error = %q, want same rejection as Update() = %q", previewErr.Error(), updateErr.Error())
	}
}

func TestPreviewBeforeUpdate_LeadValidationRuleParityWithUpdate(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-979 preview/update validation parity test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	if err := sm.Metadata.CreateValidationRule(ctx, &models.ValidationRule{
		ObjectAPIName: "Lead",
		Name:          "RejectPreviewParityStatus",
		Active:        true,
		Condition:     `Status = "Blocked"`,
		ErrorMessage:  "Preview/update parity status is blocked",
	}); err != nil {
		t.Fatalf("create validation rule: %v", err)
	}
	if err := sm.RefreshMetadataCache(ctx); err != nil {
		t.Fatalf("refresh metadata cache: %v", err)
	}

	leadRecord, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue979",
		"LastName":  "PreviewValidationParity",
		"Company":   "Issue979 Preview Validation Co",
		"Status":    "Open - Not Contacted",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Lead: %v", err)
	}
	leadID, _ := leadRecord["Id"].(string)
	if leadID == "" {
		t.Fatal("insert Lead returned empty Id")
	}

	badUpdates := models.SObject{
		"Status": "Blocked",
	}

	updateErr := sm.Persistence.Update(ctx, "Lead", leadID, badUpdates, systemAdminSession())
	if updateErr == nil {
		t.Fatal("Update() error = nil, want validation rule rejection")
	}
	_, previewErr := sm.Persistence.PreviewBeforeUpdate(ctx, "Lead", leadID, badUpdates, nil, systemAdminSession())
	if previewErr == nil {
		t.Fatalf("PreviewBeforeUpdate() error = nil, want same validation rule rejection as Update() (%v)", updateErr)
	}
	if previewErr.Error() != updateErr.Error() {
		t.Fatalf("PreviewBeforeUpdate() error = %q, want same rejection as Update() = %q", previewErr.Error(), updateErr.Error())
	}
}

func TestCommitPreparedUpdate_LeadFinalExtraUpdatesMatchUpdateFieldGuards(t *testing.T) {
	ctx := constants.WithSecurityContext(context.Background(), constants.SystemContext, "issue-349 prepared update field guard parity test")
	conn, sm := setupConvertLeadRuntime(t, ctx)
	defer func() { _ = conn.Close() }()

	leadRecord, err := sm.Persistence.Insert(ctx, "Lead", models.SObject{
		"FirstName": "Issue349",
		"LastName":  "PreparedGuard",
		"Company":   "Issue349 Prepared Guard Co",
		"Status":    "Open - Not Contacted",
	}, systemAdminSession())
	if err != nil {
		t.Fatalf("insert Lead: %v", err)
	}
	leadID, _ := leadRecord["Id"].(string)
	if leadID == "" {
		t.Fatal("insert Lead returned empty Id")
	}

	originalCreatedDate := queryTime(t, conn.DB(), "SELECT `CreatedDate` FROM `Lead` WHERE `Id` = ?", leadID)
	badCreatedDate := originalCreatedDate.Add(24 * time.Hour)
	updateErr := sm.Persistence.Update(ctx, "Lead", leadID, models.SObject{
		"Status":      "Qualified",
		"CreatedDate": badCreatedDate,
	}, systemAdminSession())
	if updateErr == nil {
		t.Fatal("Update() error = nil, want shared guard rejection for CreatedDate")
	}

	prepared, err := sm.Persistence.PreviewBeforeUpdate(ctx, "Lead", leadID, models.SObject{
		"Status": "Qualified",
	}, nil, systemAdminSession())
	if err != nil {
		t.Fatalf("PreviewBeforeUpdate() error = %v", err)
	}
	if prepared == nil {
		t.Fatal("PreviewBeforeUpdate() = nil, want prepared update")
	}

	commitErr := sm.Persistence.CommitPreparedUpdate(ctx, "Lead", leadID, prepared, models.SObject{
		"CreatedDate": badCreatedDate,
	}, systemAdminSession())
	if commitErr == nil {
		t.Fatal("CommitPreparedUpdate() error = nil, want same shared guard rejection as Update()")
	}
	if commitErr.Error() != updateErr.Error() {
		t.Fatalf("CommitPreparedUpdate() error = %q, want same rejection as Update() = %q", commitErr.Error(), updateErr.Error())
	}
	if got := queryTime(t, conn.DB(), "SELECT `CreatedDate` FROM `Lead` WHERE `Id` = ?", leadID); !got.Equal(originalCreatedDate) {
		t.Fatalf("Lead.CreatedDate after CommitPreparedUpdate = %s, want unchanged %s", got.UTC().Format(time.RFC3339Nano), originalCreatedDate.UTC().Format(time.RFC3339Nano))
	}
	if got := queryString(t, conn.DB(), "SELECT `Status` FROM `Lead` WHERE `Id` = ?", leadID); got != "Open - Not Contacted" {
		t.Fatalf("Lead.Status after rejected CommitPreparedUpdate = %q, want original %q", got, "Open - Not Contacted")
	}
}

func queryTime(t *testing.T, db *sql.DB, query string, args ...any) time.Time {
	t.Helper()
	var value time.Time
	if err := db.QueryRowContext(context.Background(), query, args...).Scan(&value); err != nil {
		t.Fatalf("query time: %v", err)
	}
	return value
}
