package controlplane

import (
	"context"
	"fmt"
	"strings"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/nexuscrm/analytics"
	"github.com/nexuscrm/kernel/pkg/logging"
	"github.com/nexuscrm/metadata"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
	"github.com/pingcap-inc/nexuscrm-controlplane/packs"
)

// TestRollupActiveFormulaPackValidatesInstallable confirms the rollup-active-formula-smoke
// fixture loads and passes manifest-level and field-shape validation via
// ValidateInstallablePackSource. This locks in Invariant 1: the pack declaring a
// rollupsummary field alongside an active formula field is accepted as installable.
//
// Note: rollupConfig cross-reference validation (validateRollupRelationshipField in
// pack_field_spec_writer.go) runs at apply time via tableDefinitionsForObjectSpecsWithResolver,
// not during ValidateInstallablePackSource. This test verifies manifest structure and
// field-shape canonicality, not rollup relationship resolution.
func TestRollupActiveFormulaPackValidatesInstallable(t *testing.T) {
	root := controlplaneTestPackFixtureDir(t, "rollup-active-formula-smoke")
	report := packs.ValidateInstallablePackSource("local_path:" + root)
	if report.HasErrors() || report.Pack == nil {
		t.Fatalf("rollup-active-formula-smoke fixture is not installable:\n%s", report.Error())
	}
}

// TestRollupActiveFormulaPackCompilesView confirms that an active formula field
// referencing a filtered rollupsummary stored column compiles to valid view DDL.
// This locks in Invariant 2: formulaMode=active fields read the materialized
// rollupsummary column from the parent projection rather than synthesizing a
// parallel aggregate join inside FormulaViewService.
//
// Coverage contract (hermetic, always-on):
//   - pack fixture passes ValidateInstallablePackSource
//   - PreviewDDL for Grant__c returns non-empty DDL
//   - DDL contains Total__c (the active formula field)
//   - No TiDB connection required: INFORMATION_SCHEMA supplement is skipped
//     gracefully when sqlmock returns an error for unexpected queries
func TestRollupActiveFormulaPackCompilesView(t *testing.T) {
	root := controlplaneTestPackFixtureDir(t, "rollup-active-formula-smoke")
	report := packs.ValidateInstallablePackSource("local_path:" + root)
	if report.HasErrors() || report.Pack == nil {
		t.Fatalf("rollup-active-formula-smoke fixture is not installable:\n%s", report.Error())
	}

	schemas := rollupActiveFormulaSchemasForTest(t, report.Pack)

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

	metaRepo := &activeFormulaSmokeMetadataRepo{schemas: schemas}
	metaSvc := metadata.NewMetadataService(metaRepo, nil, nil)
	fvSvc := analytics.NewFormulaViewService(db, metaSvc, logging.Default)
	expectFormulaViewPhysicalColumnsForSchema(mock, rollupActiveFormulaSchemaByName(t, schemas, "Grant__c"))

	ddl, err := fvSvc.PreviewDDL(context.Background(), "Grant__c")
	if err != nil {
		t.Fatalf("PreviewDDL(Grant__c): %v", err)
	}
	if strings.TrimSpace(ddl) == "" {
		t.Fatal("PreviewDDL returned empty DDL")
	}
	if !strings.Contains(ddl, "Total__c") {
		t.Fatalf("PreviewDDL DDL does not contain Total__c:\n%s", ddl)
	}
	if !strings.Contains(ddl, "InitialAmount__c") {
		t.Fatalf("PreviewDDL DDL does not contain InitialAmount__c (active formula references base field + rollup):\n%s", ddl)
	}
	if !strings.Contains(ddl, "t.`ApprovedChildSum__c`") {
		t.Fatalf("PreviewDDL DDL does not read the stored filtered rollupsummary column from the parent projection:\n%s", ddl)
	}
	if strings.Contains(ddl, "Status__c = 'Approved'") {
		t.Fatalf("PreviewDDL DDL unexpectedly inlined the rollup filter into the formula view instead of reading the stored column:\n%s", ddl)
	}
	if strings.Contains(ddl, "rsq0") {
		t.Fatalf("PreviewDDL DDL unexpectedly synthesized an aggregate join for the stored rollupsummary reference:\n%s", ddl)
	}
}

func rollupActiveFormulaSchemaByName(t *testing.T, schemas []*models.ObjectMetadata, apiName string) *models.ObjectMetadata {
	t.Helper()
	for _, schema := range schemas {
		if schema != nil && strings.EqualFold(schema.APIName, apiName) {
			return schema
		}
	}
	t.Fatalf("schema %s not found in %#v", apiName, schemas)
	return nil
}

func rollupActiveFormulaSchemasForTest(t *testing.T, pack *packs.SourceBackedPack) []*models.ObjectMetadata {
	t.Helper()

	specs, err := packs.ParseObjectSpecsPayloads(pack.RuntimeObjectSpecPayloads(), "rollup-active-formula-smoke")
	if err != nil {
		t.Fatalf("parse rollup-active-formula-smoke object specs: %v", err)
	}

	var schemas []*models.ObjectMetadata
	for specIdx, spec := range specs {
		schema := &models.ObjectMetadata{
			ID:                     fmt.Sprintf("obj_%s", strings.ToLower(spec.APIName)),
			APIName:                spec.APIName,
			Label:                  spec.Label,
			PluralLabel:            spec.PluralLabel,
			TableType:              constants.TableType(spec.TableType),
			SharingModel:           constants.SharingModel(spec.SharingModel),
			EnableHierarchySharing: spec.EnableHierarchySharing,
		}
		for idx, field := range spec.Fields {
			meta := models.FieldMetadata{
				ID:          fmt.Sprintf("fld_%d_%d", specIdx, idx),
				APIName:     field.APIName,
				Label:       field.Label,
				Type:        constants.NormalizeFieldType(field.Type),
				Required:    field.Required,
				IsNameField: field.IsNameField,
				ReferenceTo: field.ReferenceTo,
			}
			if strings.TrimSpace(field.FormulaMode) != "" {
				m := field.FormulaMode
				meta.FormulaMode = &m
			}
			if strings.TrimSpace(field.Formula) != "" {
				f := field.Formula
				meta.Formula = &f
			}
			if strings.TrimSpace(field.ReturnType) != "" {
				rt := constants.NormalizeFieldType(field.ReturnType)
				meta.ReturnType = &rt
			}
			if field.RollupConfig != nil {
				cfg := *field.RollupConfig
				meta.RollupConfig = &cfg
			}
			schema.Fields = append(schema.Fields, meta)
		}
		schema.FieldsMap = map[string]*models.FieldMetadata{}
		for i := range schema.Fields {
			f := &schema.Fields[i]
			schema.FieldsMap[strings.ToLower(f.APIName)] = f
		}
		schemas = append(schemas, schema)
	}
	return schemas
}
