//go:build integration

package controlplane

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"strings"
	"testing"

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

func TestRuntimeScopedPicklistWriteValidationAllowsValidCreate(t *testing.T) {
	ctx := context.Background()
	conn, sm := setupCRMRuntimeForContractLifecycle(t, ctx)
	defer conn.Close()

	admin := systemAdminSession()
	campaignID := createScopedPicklistCampaign(t, ctx, sm, admin, "Spring Launch")
	leadID := createScopedPicklistLead(t, ctx, sm, admin, "Ng")
	createScopedPicklistMemberStatus(t, ctx, sm, admin, campaignID, "Sent")

	member, err := sm.Persistence().Insert(ctx, "CampaignMember", models.SObject{
		"CampaignId": campaignID,
		"LeadId":     leadID,
		"Status":     "Sent",
	}, admin)
	if err != nil {
		t.Fatalf("create campaign member with valid scoped status: %v", err)
	}
	if got, _ := member["Status"].(string); got != "Sent" {
		t.Fatalf("CampaignMember.Status = %q, want Sent", got)
	}
}

func TestRuntimeScopedPicklistWriteValidationIgnoresStaticOptionsForCreateAndUpdate(t *testing.T) {
	ctx := context.Background()
	conn, sm := setupCRMRuntimeForContractLifecycle(t, ctx)
	defer conn.Close()
	forceCampaignMemberStatusStaticOptions(t, ctx, sm)

	admin := systemAdminSession()
	campaignID := createScopedPicklistCampaign(t, ctx, sm, admin, "Static Runtime Split")
	leadID := createScopedPicklistLead(t, ctx, sm, admin, "Split")
	staticOnlyLeadID := createScopedPicklistLead(t, ctx, sm, admin, "Static Only")
	createScopedPicklistMemberStatus(t, ctx, sm, admin, campaignID, "FollowUp")
	createScopedPicklistMemberStatus(t, ctx, sm, admin, campaignID, "Nurture")

	member, err := sm.Persistence().Insert(ctx, "CampaignMember", models.SObject{
		"CampaignId": campaignID,
		"LeadId":     leadID,
		"Status":     "FollowUp",
	}, admin)
	if err != nil {
		t.Fatalf("create campaign member with runtime-only status: %v", err)
	}
	memberID, _ := member["Id"].(string)
	if memberID == "" {
		t.Fatal("create campaign member returned empty Id")
	}

	if err := sm.Persistence().Update(ctx, "CampaignMember", memberID, models.SObject{
		"Status": "Nurture",
	}, admin); err != nil {
		t.Fatalf("update campaign member with runtime-only status: %v", err)
	}

	_, err = sm.Persistence().Insert(ctx, "CampaignMember", models.SObject{
		"CampaignId": campaignID,
		"LeadId":     staticOnlyLeadID,
		"Status":     "Sent",
	}, admin)
	if err == nil {
		t.Fatal("create campaign member with static-only status error = nil, want runtime scoped rejection")
	}
	if !strings.Contains(err.Error(), "value is not valid for CampaignId") {
		t.Fatalf("static-only status error = %v, want runtime scoped validation error", err)
	}
}

func TestRuntimeScopedPicklistWriteValidationRejectsControllerChangedUpdate(t *testing.T) {
	ctx := context.Background()
	conn, sm := setupCRMRuntimeForContractLifecycle(t, ctx)
	defer conn.Close()

	admin := systemAdminSession()
	campaignA := createScopedPicklistCampaign(t, ctx, sm, admin, "Spring Launch")
	campaignB := createScopedPicklistCampaign(t, ctx, sm, admin, "Fall Launch")
	leadID := createScopedPicklistLead(t, ctx, sm, admin, "Lee")
	createScopedPicklistMemberStatus(t, ctx, sm, admin, campaignA, "Sent")
	createScopedPicklistMemberStatus(t, ctx, sm, admin, campaignB, "Responded")

	member, err := sm.Persistence().Insert(ctx, "CampaignMember", models.SObject{
		"CampaignId": campaignA,
		"LeadId":     leadID,
		"Status":     "Sent",
	}, admin)
	if err != nil {
		t.Fatalf("create campaign member seed row: %v", err)
	}
	memberID, _ := member["Id"].(string)
	if memberID == "" {
		t.Fatal("create campaign member returned empty Id")
	}

	err = sm.Persistence().Update(ctx, "CampaignMember", memberID, models.SObject{
		"CampaignId": campaignB,
	}, admin)
	if err == nil {
		t.Fatal("controller-changed scoped update error = nil, want validation failure")
	}
	if !strings.Contains(err.Error(), `value "Sent" is not valid for CampaignId`) {
		t.Fatalf("controller-changed scoped update error = %v, want scoped validation error", err)
	}
}

func TestRuntimeScopedPicklistWriteValidationAllowsBulkInsert(t *testing.T) {
	ctx := context.Background()
	conn, sm := setupCRMRuntimeForContractLifecycle(t, ctx)
	defer conn.Close()

	admin := systemAdminSession()
	campaignID := createScopedPicklistCampaign(t, ctx, sm, admin, "Spring Launch")
	lead1ID := createScopedPicklistLead(t, ctx, sm, admin, "Slice")
	lead2ID := createScopedPicklistLead(t, ctx, sm, admin, "Patch")
	createScopedPicklistMemberStatus(t, ctx, sm, admin, campaignID, "Sent")
	createScopedPicklistMemberStatus(t, ctx, sm, admin, campaignID, "Responded")

	result, err := sm.Persistence().BulkInsert(ctx, "CampaignMember", []models.SObject{
		{
			"CampaignId": campaignID,
			"LeadId":     lead1ID,
			"Status":     "Sent",
		},
		{
			"CampaignId": campaignID,
			"LeadId":     lead2ID,
			"Status":     "Responded",
		},
	}, admin, records.BulkInsertOptions{})
	if err != nil {
		t.Fatalf("bulk insert campaign members with valid scoped status: %v", err)
	}
	if result.SuccessCount != 2 || result.FailedCount != 0 {
		t.Fatalf("bulk insert result = %+v, want success=2 failed=0", result)
	}
}

func TestRuntimeScopedPicklistWriteValidationAllowsBulkInsertWithRuntimeOnlyValues(t *testing.T) {
	ctx := context.Background()
	conn, sm := setupCRMRuntimeForContractLifecycle(t, ctx)
	defer conn.Close()
	forceCampaignMemberStatusStaticOptions(t, ctx, sm)

	admin := systemAdminSession()
	campaignID := createScopedPicklistCampaign(t, ctx, sm, admin, "Bulk Static Runtime Split")
	lead1ID := createScopedPicklistLead(t, ctx, sm, admin, "Bulk One")
	lead2ID := createScopedPicklistLead(t, ctx, sm, admin, "Bulk Two")
	createScopedPicklistMemberStatus(t, ctx, sm, admin, campaignID, "FollowUp")
	createScopedPicklistMemberStatus(t, ctx, sm, admin, campaignID, "Nurture")

	result, err := sm.Persistence().BulkInsert(ctx, "CampaignMember", []models.SObject{
		{
			"CampaignId": campaignID,
			"LeadId":     lead1ID,
			"Status":     "FollowUp",
		},
		{
			"CampaignId": campaignID,
			"LeadId":     lead2ID,
			"Status":     "Nurture",
		},
	}, admin, records.BulkInsertOptions{})
	if err != nil {
		t.Fatalf("bulk insert campaign members with runtime-only statuses: %v", err)
	}
	if result.SuccessCount != 2 || result.FailedCount != 0 {
		t.Fatalf("bulk insert result = %+v, want success=2 failed=0", result)
	}
}

func TestRuntimeScopedPicklistWriteValidationRejectsOmittedStatusWithValidationError(t *testing.T) {
	ctx := context.Background()
	conn, sm := setupCRMRuntimeForContractLifecycle(t, ctx)
	defer conn.Close()
	forceCampaignMemberStatusStaticOptions(t, ctx, sm)

	admin := systemAdminSession()
	campaignID := createScopedPicklistCampaign(t, ctx, sm, admin, "Omitted Runtime Status")
	leadID := createScopedPicklistLead(t, ctx, sm, admin, "Omitted")

	_, err := sm.Persistence().Insert(ctx, "CampaignMember", models.SObject{
		"CampaignId": campaignID,
		"LeadId":     leadID,
	}, admin)
	if err == nil {
		t.Fatal("create campaign member with omitted runtime-scoped status error = nil, want validation error")
	}
	if !strings.Contains(err.Error(), "validation error on field 'Status': value is required when CampaignId is set") {
		t.Fatalf("omitted runtime-scoped status error = %v, want field validation error", err)
	}
}

func TestRuntimeScopedPicklistSeedStyleReplayAllowsCampaignMemberBaselineRows(t *testing.T) {
	ctx := context.Background()
	conn, sm := setupCRMRuntimeForContractLifecycle(t, ctx)
	defer conn.Close()

	admin := systemAdminSession()
	campaignID := createScopedPicklistCampaign(t, ctx, sm, admin, "Seed Replay Campaign")
	leadID := createScopedPicklistLead(t, ctx, sm, admin, "Replay")
	contactID := createScopedPicklistContact(t, ctx, sm, admin, "Replay Contact")

	upsertScopedPicklistMemberStatusSeed(t, ctx, sm, admin, campaignID, "Sent", 1.0, true, false)
	upsertScopedPicklistMemberStatusSeed(t, ctx, sm, admin, campaignID, "Responded", 2.0, false, true)

	memberLeadID := upsertScopedPicklistMemberSeed(t, ctx, sm, admin, models.SObject{
		"CampaignId": campaignID,
		"LeadId":     leadID,
		"Status":     "Sent",
	})
	memberContactID := upsertScopedPicklistMemberSeed(t, ctx, sm, admin, models.SObject{
		"CampaignId": campaignID,
		"ContactId":  contactID,
		"Status":     "Responded",
	})

	// Replay the same payloads through the normal match-then-PATCH path.
	if got := upsertScopedPicklistMemberSeed(t, ctx, sm, admin, models.SObject{
		"CampaignId": campaignID,
		"LeadId":     leadID,
		"Status":     "Sent",
	}); got != memberLeadID {
		t.Fatalf("seed replay lead CampaignMember Id = %q, want existing %q", got, memberLeadID)
	}
	if got := upsertScopedPicklistMemberSeed(t, ctx, sm, admin, models.SObject{
		"CampaignId": campaignID,
		"ContactId":  contactID,
		"Status":     "Responded",
	}); got != memberContactID {
		t.Fatalf("seed replay contact CampaignMember Id = %q, want existing %q", got, memberContactID)
	}

	statusLead, err := campaignMemberStatusValue(ctx, sm, memberLeadID)
	if err != nil {
		t.Fatalf("query lead CampaignMember status: %v", err)
	}
	if statusLead != "Sent" {
		t.Fatalf("lead CampaignMember.Status = %q, want Sent", statusLead)
	}
	statusContact, err := campaignMemberStatusValue(ctx, sm, memberContactID)
	if err != nil {
		t.Fatalf("query contact CampaignMember status: %v", err)
	}
	if statusContact != "Responded" {
		t.Fatalf("contact CampaignMember.Status = %q, want Responded", statusContact)
	}
}

func forceCampaignMemberStatusStaticOptions(t *testing.T, ctx context.Context, sm ServiceGraph) {
	t.Helper()

	schema := sm.Metadata().GetSchema(ctx, "CampaignMember")
	if schema == nil {
		t.Fatal("CampaignMember schema not found")
	}
	for i := range schema.Fields {
		if !strings.EqualFold(schema.Fields[i].APIName, "Status") {
			continue
		}
		controllerField := "CampaignId"
		validator := models.RuntimeScopedPicklistValidator
		field := &schema.Fields[i]
		field.Type = constants.FieldTypePicklist
		field.Options = models.PicklistOptionsFromStrings([]string{"Sent", "Responded"})
		field.Validator = &validator
		field.ControllingField = &controllerField
		field.ReferenceTo = []string{"CampaignMemberStatus"}
		if schema.FieldsMap != nil {
			schema.FieldsMap[strings.ToLower(field.APIName)] = field
		}
		return
	}
	t.Fatal("CampaignMember.Status field not found")
}

func createScopedPicklistCampaign(t *testing.T, ctx context.Context, sm ServiceGraph, user *models.UserSession, name string) string {
	t.Helper()

	campaign, err := sm.Persistence().Insert(ctx, "Campaign", models.SObject{
		"Name":     name,
		"IsActive": true,
	}, user)
	if err != nil {
		t.Fatalf("create campaign: %v", err)
	}
	id, _ := campaign["Id"].(string)
	if id == "" {
		t.Fatal("create campaign returned empty Id")
	}
	return id
}

func createScopedPicklistLead(t *testing.T, ctx context.Context, sm ServiceGraph, user *models.UserSession, lastName string) string {
	t.Helper()

	lead, err := sm.Persistence().Insert(ctx, "Lead", models.SObject{
		"LastName": lastName,
		"Company":  "NexusCRM",
	}, user)
	if err != nil {
		t.Fatalf("create lead: %v", err)
	}
	id, _ := lead["Id"].(string)
	if id == "" {
		t.Fatal("create lead returned empty Id")
	}
	return id
}

func createScopedPicklistContact(t *testing.T, ctx context.Context, sm ServiceGraph, user *models.UserSession, lastName string) string {
	t.Helper()

	contact, err := sm.Persistence().Insert(ctx, "Contact", models.SObject{
		"LastName": lastName,
	}, user)
	if err != nil {
		t.Fatalf("create contact: %v", err)
	}
	id, _ := contact["Id"].(string)
	if id == "" {
		t.Fatal("create contact returned empty Id")
	}
	return id
}

func createScopedPicklistMemberStatus(t *testing.T, ctx context.Context, sm ServiceGraph, user *models.UserSession, campaignID, label string) string {
	t.Helper()

	status, err := sm.Persistence().Insert(ctx, "CampaignMemberStatus", models.SObject{
		"CampaignId":   campaignID,
		"Label":        label,
		"SortOrder":    1.0,
		"IsDefault":    false,
		"HasResponded": false,
	}, user)
	if err != nil {
		t.Fatalf("create campaign member status: %v", err)
	}
	id, _ := status["Id"].(string)
	if id == "" {
		t.Fatal("create campaign member status returned empty Id")
	}
	return id
}

func upsertScopedPicklistMemberStatusSeed(
	t *testing.T,
	ctx context.Context,
	sm ServiceGraph,
	user *models.UserSession,
	campaignID, label string,
	sortOrder float64,
	isDefault, hasResponded bool,
) string {
	t.Helper()

	existingID, err := findSeedRecordID(ctx, sm, "CampaignMemberStatus", models.SObject{
		"CampaignId": campaignID,
		"Label":      label,
	})
	if err != nil {
		t.Fatalf("find CampaignMemberStatus seed row: %v", err)
	}
	payload := models.SObject{
		"CampaignId":   campaignID,
		"Label":        label,
		"SortOrder":    sortOrder,
		"IsDefault":    isDefault,
		"HasResponded": hasResponded,
	}
	if existingID == "" {
		created, err := sm.Persistence().Insert(ctx, "CampaignMemberStatus", payload, user)
		if err != nil {
			t.Fatalf("insert CampaignMemberStatus seed row: %v", err)
		}
		id, _ := created["Id"].(string)
		if id == "" {
			t.Fatal("insert CampaignMemberStatus seed row returned empty Id")
		}
		return id
	}
	if err := sm.Persistence().Update(ctx, "CampaignMemberStatus", existingID, payload, user); err != nil {
		t.Fatalf("update CampaignMemberStatus seed row: %v", err)
	}
	return existingID
}

func upsertScopedPicklistMemberSeed(
	t *testing.T,
	ctx context.Context,
	sm ServiceGraph,
	user *models.UserSession,
	payload models.SObject,
) string {
	t.Helper()

	match := models.SObject{
		"CampaignId": payload["CampaignId"],
	}
	if leadID, ok := payload["LeadId"]; ok {
		match["LeadId"] = leadID
	}
	if contactID, ok := payload["ContactId"]; ok {
		match["ContactId"] = contactID
	}

	existingID, err := findSeedRecordID(ctx, sm, "CampaignMember", match)
	if err != nil {
		t.Fatalf("find CampaignMember seed row: %v", err)
	}
	if existingID == "" {
		created, err := sm.Persistence().Insert(ctx, "CampaignMember", payload, user)
		if err != nil {
			t.Fatalf("insert CampaignMember seed row: %v", err)
		}
		id, _ := created["Id"].(string)
		if id == "" {
			t.Fatal("insert CampaignMember seed row returned empty Id")
		}
		return id
	}
	if err := sm.Persistence().Update(ctx, "CampaignMember", existingID, payload, user); err != nil {
		t.Fatalf("update CampaignMember seed row: %v", err)
	}
	return existingID
}

func findSeedRecordID(ctx context.Context, sm ServiceGraph, object string, match models.SObject) (string, error) {
	if sm == nil || sm.DB() == nil {
		return "", fmt.Errorf("persistence DB unavailable")
	}
	var (
		args       []interface{}
		conditions []string
	)
	for _, field := range []string{"CampaignId", "LeadId", "ContactId", "Label"} {
		if value, ok := match[field]; ok {
			conditions = append(conditions, fmt.Sprintf("`%s` = ?", field))
			args = append(args, value)
		}
	}
	if len(conditions) == 0 {
		return "", fmt.Errorf("seed match for %s is empty", object)
	}
	query := fmt.Sprintf("SELECT Id FROM `%s` WHERE %s LIMIT 1", object, strings.Join(conditions, " AND "))
	var id string
	err := sm.DB().QueryRowContext(ctx, query, args...).Scan(&id)
	if err != nil {
		if errors.Is(err, sql.ErrNoRows) {
			return "", nil
		}
		return "", err
	}
	return id, nil
}

func campaignMemberStatusValue(ctx context.Context, sm ServiceGraph, id string) (string, error) {
	var status string
	if err := sm.DB().QueryRowContext(ctx, "SELECT Status FROM `CampaignMember` WHERE Id = ?", id).Scan(&status); err != nil {
		return "", err
	}
	return status, nil
}
