package dialectsfcrm

import (
	"context"
	"net/http"
	"testing"

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

// rollupOverPercentField builds a rollup-summary field whose scalar result type
// is percent, in the shape describe/UI-API/report see at runtime: the
// resolver-owned RollupConfig plus the persistence-hydrated ReturnType/Precision/
// Scale (hydrateRollupResultMetadata copies RollupConfig.ResultType/Result* onto
// the top-level field at load time).
func rollupOverPercentField() models.FieldMetadata {
	percent := constants.FieldTypePercent
	precision := 5
	scale := 2
	return models.FieldMetadata{
		APIName: "Coverage_Rate__c",
		Label:   "Coverage Rate",
		Type:    constants.FieldTypeRollupSummary,
		RollupConfig: &models.RollupConfig{
			SummaryObject:     "Coverage_Line__c",
			SummaryField:      "Pct__c",
			RelationshipField: "ParentId__c",
			CalcType:          "MAX",
			ResultType:        &percent,
			ResultPrecision:   &precision,
			ResultScale:       &scale,
		},
		ReturnType: &percent,
		Precision:  &precision,
		Scale:      &scale,
	}
}

// TestDescribeObjectRollupOverPercentReportsPercentScalar pins the describe
// projection: a rollup over a percent source must describe as
// type: percent, calculated: true (source scalar, not the "rollupsummary"
// wrapper), with the hydrated precision/scale. #374 fixed the scalar Type; this
// additionally locks calculated=true (a rollup is a computed field) and guards
// the type/precision against regression.
func TestDescribeObjectRollupOverPercentReportsPercentScalar(t *testing.T) {
	object := models.ObjectMetadata{
		APIName:      "Coverage__c",
		Label:        "Coverage",
		PluralLabel:  "Coverages",
		Searchable:   true,
		SharingModel: constants.SharingModelPublicReadWrite,
		TableType:    constants.TableTypeCustomObject,
		Fields: []models.FieldMetadata{
			{APIName: "Id", Label: "Coverage ID", Type: constants.FieldTypeString, IsSystem: true},
			{APIName: "Name", Label: "Coverage Name", Type: constants.FieldTypeString, IsNameField: true},
			rollupOverPercentField(),
		},
	}
	metadata := &fakeDescribeMetadata{
		objects:     []models.ObjectMetadata{object},
		schemas:     map[string]*models.ObjectMetadata{"Coverage__c": &object},
		keyPrefixes: map[string]string{"Coverage__c": "a01"},
	}
	policy := &fakeDescribePolicy{access: map[string]*contract.EffectiveObjectAccess{
		"Coverage__c": {
			Object: contract.ObjectAccess{Create: true, Read: true, Update: true, Delete: true},
			Fields: map[string]contract.FieldAccess{
				"Id":               {Readable: true},
				"Name":             {Createable: true, Updateable: true, Readable: true},
				"Coverage_Rate__c": {Readable: true},
			},
		},
	}}

	resp, err := DescribeObjectNeutralHandler(context.Background(), &dialect.Request{
		Version: "v63.0",
		User:    &models.UserSession{ID: "005"},
		Params:  map[string]string{"sobject": "Coverage__c"},
	}, dialectDescribeDeps(metadata, policy))
	if err != nil {
		t.Fatalf("DescribeObjectNeutralHandler error = %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200", resp.Status)
	}
	body, ok := resp.Body.(describeSObjectResult)
	if !ok {
		t.Fatalf("resp.Body type = %T, want describeSObjectResult", resp.Body)
	}
	field, ok := findDescribeField(body, "Coverage_Rate__c")
	if !ok {
		t.Fatalf("Coverage_Rate__c not present in describe fields")
	}

	if field.Type != "percent" {
		t.Errorf("rollup-over-percent Type = %q, want \"percent\"", field.Type)
	}
	if field.SoapType != mapFieldTypeToSoapType(models.FieldType("percent")) {
		t.Errorf("SoapType = %q, want %q", field.SoapType, mapFieldTypeToSoapType(models.FieldType("percent")))
	}
	if !field.Calculated {
		t.Errorf("Calculated = false, want true for a rollup-summary field")
	}
	if field.CalculatedFormula != nil {
		t.Errorf("CalculatedFormula = %q, want nil for a rollup-summary field", *field.CalculatedFormula)
	}
	if field.Precision != 5 {
		t.Errorf("Precision = %d, want 5", field.Precision)
	}
	if field.Scale != 2 {
		t.Errorf("Scale = %d, want 2", field.Scale)
	}
}

func findDescribeField(result describeSObjectResult, name string) (sObjectField, bool) {
	for _, f := range result.Fields {
		if f.Name == name {
			return f, true
		}
	}
	return sObjectField{}, false
}

// TestUIAPIObjectInfoRollupOverPercentReportsPercentDataType pins the UI-API
// object-info field metadata (uiAPIFieldInfo.DataType), which previously emitted
// the raw stored type: a rollup showed dataType:"rollupsummary" and a formula
// showed dataType:"formula". Both must now resolve to the source scalar,
// consistent with describe.
func TestUIAPIObjectInfoRollupOverPercentReportsPercentDataType(t *testing.T) {
	percent := constants.FieldTypePercent
	stub := newUIAPITestContracts()
	stub.schema.Fields = append(stub.schema.Fields,
		rollupOverPercentField(),
		// Formula over percent: object-info previously emitted the raw "formula"
		// type; it must resolve to the percent scalar through the same helper.
		models.FieldMetadata{
			APIName:    "Discount_Pct__c",
			Label:      "Discount Pct",
			Type:       constants.FieldTypeFormula,
			ReturnType: &percent,
		},
	)

	resp, err := UIAPIObjectInfoNeutralHandler(context.Background(), &dialect.Request{
		Params: map[string]string{"objectApiName": "Account"},
	}, uiAPIDeps(stub, nil))
	if err != nil {
		t.Fatalf("UIAPIObjectInfoNeutralHandler error = %v", err)
	}
	if resp.Status != http.StatusOK {
		t.Fatalf("status = %d, want 200", resp.Status)
	}
	info, ok := resp.Body.(*uiAPIObjectInfo)
	if !ok {
		t.Fatalf("resp.Body type = %T, want *uiAPIObjectInfo", resp.Body)
	}
	for _, name := range []string{"Coverage_Rate__c", "Discount_Pct__c"} {
		fi, ok := info.Fields[name]
		if !ok {
			t.Fatalf("%s missing from object-info fields", name)
		}
		if fi.DataType != "percent" {
			t.Errorf("%s object-info DataType = %q, want \"percent\" (regression: raw computed-field type)", name, fi.DataType)
		}
	}
}

// TestReportDataTypeRollupOverPercentReportsPercentScalar pins report column /
// aggregate dataType (resolveSalesforceFieldDataType), which previously read the
// raw field type so a rollup/formula over percent collapsed to the report
// mapper's "string" default; both must now resolve to "percent".
func TestReportDataTypeRollupOverPercentReportsPercentScalar(t *testing.T) {
	percent := constants.FieldTypePercent
	rollup := rollupOverPercentField()
	formula := models.FieldMetadata{
		APIName:    "Discount_Pct__c",
		Type:       constants.FieldTypeFormula,
		ReturnType: &percent,
	}
	schema := &models.ObjectMetadata{
		APIName: "Coverage__c",
		FieldsMap: map[string]*models.FieldMetadata{
			"Coverage_Rate__c": &rollup,
			"Discount_Pct__c":  &formula,
		},
	}
	for _, col := range []string{"Coverage_Rate__c", "Discount_Pct__c"} {
		if got := resolveSalesforceFieldDataType(col, schema); got != "percent" {
			t.Errorf("resolveSalesforceFieldDataType(%q) = %q, want \"percent\" (regression: computed field collapsed to string)", col, got)
		}
	}
}

// TestReportDataTypeNumericComputedFieldsResolveScalar guards the whole numeric
// class, not just one alias: a computed field whose scalar ReturnType is any
// numeric alias must report a numeric report dataType rather than collapsing to
// "string". Covers the COUNT rollup ("int") and the "number" formula alias
// (mapped to "double", consistent with describe) — the gaps the adversarial
// review flagged when the report mapper kept its own partial type list.
func TestReportDataTypeNumericComputedFieldsResolveScalar(t *testing.T) {
	newField := func(apiName string, ret models.FieldType) *models.FieldMetadata {
		rt := ret
		return &models.FieldMetadata{
			APIName:    apiName,
			Type:       constants.FieldTypeFormula,
			ReturnType: &rt,
		}
	}
	intType := models.FieldType("int")
	countRollup := &models.FieldMetadata{
		APIName: "Number_Of_Lines__c",
		Type:    constants.FieldTypeRollupSummary,
		RollupConfig: &models.RollupConfig{
			SummaryObject:     "Coverage_Line__c",
			RelationshipField: "ParentId__c",
			CalcType:          "COUNT",
			ResultType:        &intType,
		},
		ReturnType: &intType,
	}
	numberFormula := newField("Weighted_Amount__c", models.FieldType("number"))
	doubleFormula := newField("Score__c", constants.FieldTypeDouble)
	schema := &models.ObjectMetadata{
		APIName: "Coverage__c",
		FieldsMap: map[string]*models.FieldMetadata{
			"Number_Of_Lines__c": countRollup,
			"Weighted_Amount__c": numberFormula,
			"Score__c":           doubleFormula,
		},
	}
	cases := map[string]string{
		"Number_Of_Lines__c": "int",    // COUNT rollup, returnType "int"
		"Weighted_Amount__c": "double", // "number" formula alias -> double
		"Score__c":           "double", // canonical double
	}
	for col, want := range cases {
		if got := resolveSalesforceFieldDataType(col, schema); got != want {
			t.Errorf("resolveSalesforceFieldDataType(%q) = %q, want %q", col, got, want)
		}
	}
}
