//go:build integration

package controlplane

import (
	"context"
	"strconv"
	"testing"

	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
	"github.com/nexuscrm/db"
	"github.com/nexuscrm/shared/pkg/models"
)

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

	admin := systemAdminSession()
	accountID := createAccountForOrderActivation(t, ctx, sm, admin)
	pricebookID := createPricebookForOpportunityRollup(t, ctx, sm, admin)
	productID := createProductForOpportunityRollup(t, ctx, sm, admin)
	pricebookEntryID := createPricebookEntryForOpportunityRollup(t, ctx, sm, admin, pricebookID, productID, 25)
	opportunityID := createOpportunityForOpportunityRollup(t, ctx, sm, admin, accountID)

	lineItem1WithClientOverride, err := sm.Persistence().Insert(ctx, "OpportunityLineItem", models.SObject{
		"OpportunityId":    opportunityID,
		"PricebookEntryId": pricebookEntryID,
		"Quantity":         2,
		"UnitPrice":        25,
		"TotalPrice":       999,
	}, admin)
	if err != nil {
		t.Fatalf("create opportunity line item with client-supplied TotalPrice: %v", err)
	}
	lineItem1ID, _ := lineItem1WithClientOverride["Id"].(string)
	if lineItem1ID == "" {
		t.Fatal("create opportunity line item with client-supplied TotalPrice returned empty Id")
	}
	lineItem1, err := sm.Persistence().GetRaw(ctx, "OpportunityLineItem", lineItem1ID)
	if err != nil {
		t.Fatalf("get opportunity line item with client-supplied TotalPrice: %v", err)
	}
	assertCurrencyField(t, lineItem1, "TotalPrice", 50)
	assertOpportunityAmount(t, ctx, sm, opportunityID, 50)

	lineItem2 := insertOpportunityLineItemForRollup(t, ctx, sm, admin, opportunityID, pricebookEntryID, 1, 10)
	assertCurrencyField(t, lineItem2, "TotalPrice", 10)
	assertOpportunityAmount(t, ctx, sm, opportunityID, 60)
}

func setupCRMRuntimeForOpportunityLineItemRollup(t *testing.T, ctx context.Context) (*database.TiDBConnection, ServiceGraph) {
	t.Helper()

	cfg, cleanup := newTemporaryIntegrationDatabase(t)
	t.Cleanup(cleanup)
	setDatabaseEnvFromConfig(t, cfg)

	conn := openIntegrationConnection(t, cfg)
	if err := InitL0(ctx, conn); err != nil {
		_ = conn.Close()
		t.Fatalf("init-l0: %v", err)
	}
	_ = conn.Close()

	conn = openIntegrationConnection(t, cfg)
	r, err := NewRunner(conn)
	if err != nil {
		t.Fatalf("new runner: %v", err)
	}
	if err := r.RefreshCache(ctx); err != nil {
		t.Fatalf("initial cache refresh: %v", err)
	}
	t.Setenv(packs.OfficialAppPacksRootEnvVar, officialPackRootFromControlplaneTest(t))
	if err := installSourceBackedCRMPack(t, ctx, r); err != nil {
		t.Fatalf("install CRM pack: %v", err)
	}

	// The control-plane Runner's service graph (built via the public
	// BuildServiceGraph seam) IS the record-setup graph: it is constructed from
	// the same infrastructure deps a standalone ServiceManager would be, so its
	// Persistence/QuerySvc/Metadata expose the full record/query API these tests
	// drive. Reuse it instead of importing the backend composition root.
	sm := r.SvcMgr
	if err := sm.RefreshMetadataCache(ctx); err != nil {
		t.Fatalf("runtime metadata refresh: %v", err)
	}
	return conn, sm
}

func createPricebookForOpportunityRollup(t *testing.T, ctx context.Context, sm ServiceGraph, user *models.UserSession) string {
	t.Helper()
	pricebook, err := sm.Persistence().Insert(ctx, "Pricebook2", models.SObject{
		"Name":     "Standard Sales Book",
		"IsActive": true,
	}, user)
	if err != nil {
		t.Fatalf("create pricebook: %v", err)
	}
	id, _ := pricebook["Id"].(string)
	if id == "" {
		t.Fatal("create pricebook returned empty Id")
	}
	return id
}

func createProductForOpportunityRollup(t *testing.T, ctx context.Context, sm ServiceGraph, user *models.UserSession) string {
	t.Helper()
	product, err := sm.Persistence().Insert(ctx, "Product2", models.SObject{
		"Name":     "Rollup Proof Product",
		"IsActive": true,
	}, user)
	if err != nil {
		t.Fatalf("create product: %v", err)
	}
	id, _ := product["Id"].(string)
	if id == "" {
		t.Fatal("create product returned empty Id")
	}
	return id
}

func createPricebookEntryForOpportunityRollup(t *testing.T, ctx context.Context, sm ServiceGraph, user *models.UserSession, pricebookID, productID string, unitPrice float64) string {
	t.Helper()
	entry, err := sm.Persistence().Insert(ctx, "PricebookEntry", models.SObject{
		"Pricebook2Id": pricebookID,
		"Product2Id":   productID,
		"UnitPrice":    unitPrice,
		"IsActive":     true,
	}, user)
	if err != nil {
		t.Fatalf("create pricebook entry: %v", err)
	}
	id, _ := entry["Id"].(string)
	if id == "" {
		t.Fatal("create pricebook entry returned empty Id")
	}
	return id
}

func createOpportunityForOpportunityRollup(t *testing.T, ctx context.Context, sm ServiceGraph, user *models.UserSession, accountID string) string {
	t.Helper()
	opportunity, err := sm.Persistence().Insert(ctx, "Opportunity", models.SObject{
		"Name":      "Rollup Proof Opportunity",
		"AccountId": accountID,
		"StageName": "Prospecting",
		"CloseDate": "2026-07-01",
	}, user)
	if err != nil {
		t.Fatalf("create opportunity: %v", err)
	}
	id, _ := opportunity["Id"].(string)
	if id == "" {
		t.Fatal("create opportunity returned empty Id")
	}
	return id
}

func insertOpportunityLineItemForRollup(t *testing.T, ctx context.Context, sm ServiceGraph, user *models.UserSession, opportunityID, pricebookEntryID string, quantity int, unitPrice float64) models.SObject {
	t.Helper()
	lineItem, err := sm.Persistence().Insert(ctx, "OpportunityLineItem", models.SObject{
		"OpportunityId":    opportunityID,
		"PricebookEntryId": pricebookEntryID,
		"Quantity":         quantity,
		"UnitPrice":        unitPrice,
	}, user)
	if err != nil {
		t.Fatalf("create opportunity line item: %v", err)
	}
	id, _ := lineItem["Id"].(string)
	if id == "" {
		t.Fatal("create opportunity line item returned empty Id")
	}
	record, err := sm.Persistence().GetRaw(ctx, "OpportunityLineItem", id)
	if err != nil {
		t.Fatalf("get opportunity line item: %v", err)
	}
	return record
}

func assertOpportunityAmount(t *testing.T, ctx context.Context, sm ServiceGraph, opportunityID string, want float64) {
	t.Helper()
	opportunity, err := sm.Persistence().GetRaw(ctx, "Opportunity", opportunityID)
	if err != nil {
		t.Fatalf("get opportunity: %v", err)
	}
	assertCurrencyField(t, opportunity, "Amount", want)
}

func assertCurrencyField(t *testing.T, record models.SObject, field string, want float64) {
	t.Helper()
	got, err := numericField(record[field])
	if err != nil {
		t.Fatalf("%s parse error: %v (raw=%#v)", field, err, record[field])
	}
	if got != want {
		t.Fatalf("%s = %v, want %v", field, got, want)
	}
}

func numericField(raw any) (float64, error) {
	switch v := raw.(type) {
	case float64:
		return v, nil
	case float32:
		return float64(v), nil
	case int:
		return float64(v), nil
	case int64:
		return float64(v), nil
	case string:
		return strconv.ParseFloat(v, 64)
	case []byte:
		return strconv.ParseFloat(string(v), 64)
	default:
		return 0, strconv.ErrSyntax
	}
}
