package metadata

import (
	"context"
	"strings"
	"testing"

	"github.com/nexuscrm/kernel/pkg/ports"
	domainschema "github.com/nexuscrm/kernel/pkg/schema"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

type rollupSchemaMetadataRepo struct {
	ports.MetadataRepository
	schemas map[string]*models.ObjectMetadata
}

func (r *rollupSchemaMetadataRepo) GetSchemaByAPIName(_ context.Context, apiName string) (*models.ObjectMetadata, error) {
	if r == nil {
		return nil, nil
	}
	return r.schemas[strings.ToLower(strings.TrimSpace(apiName))], nil
}

func (r *rollupSchemaMetadataRepo) GetAllSchemas(_ context.Context) ([]*models.ObjectMetadata, error) {
	out := make([]*models.ObjectMetadata, 0, len(r.schemas))
	for _, schema := range r.schemas {
		out = append(out, schema)
	}
	return out, nil
}

func (r *rollupSchemaMetadataRepo) GetAllRelationships(context.Context) ([]*models.Relationship, error) {
	return nil, nil
}

func (r *rollupSchemaMetadataRepo) GetRelatedListConfigs(context.Context, string) ([]ports.RelatedListConfig, error) {
	return nil, nil
}

func (r *rollupSchemaMetadataRepo) UpsertLayout(context.Context, *models.PageLayout) error {
	return nil
}

func (r *rollupSchemaMetadataRepo) SaveLayout(context.Context, *models.PageLayout) error {
	return nil
}

func (r *rollupSchemaMetadataRepo) SetDefaultLayout(context.Context, string, string, *string) error {
	return nil
}

type rollupSchemaDDLRepo struct {
	ports.SchemaRepository
	created []domainschema.TableDefinition
}

func (r *rollupSchemaDDLRepo) CreateTableWithStrictMetadata(_ context.Context, def domainschema.TableDefinition, _ *models.ObjectMetadata) error {
	r.created = append(r.created, def)
	return nil
}

func (r *rollupSchemaDDLRepo) MapFieldTypeToSQL(fieldType string) string {
	switch constants.NormalizeFieldType(fieldType) {
	case constants.FieldTypeCurrency, constants.FieldTypeDouble, constants.FieldTypePercent:
		return "DECIMAL(65,30)"
	case constants.FieldTypeReference, constants.FieldTypeMasterDetail:
		return "VARCHAR(255)"
	case constants.FieldTypeBoolean:
		return "BOOLEAN"
	default:
		return "VARCHAR(255)"
	}
}

func testRollupChildSchema() *models.ObjectMetadata {
	return &models.ObjectMetadata{
		ID:      "obj_child",
		APIName: "Child__c",
		Fields: []models.FieldMetadata{
			{APIName: "Amount__c", Type: constants.FieldTypeCurrency},
			{APIName: "ParentId", Type: constants.FieldTypeReference, ReferenceTo: []string{"Parent__c"}},
		},
	}
}

func testRollupParentSchema(filter *string) *models.ObjectMetadata {
	return &models.ObjectMetadata{
		APIName: "Parent__c",
		Label:   "Parent",
		Fields: []models.FieldMetadata{{
			APIName: "Total__c",
			Label:   "Total",
			Type:    constants.FieldTypeRollupSummary,
			RollupConfig: &models.RollupConfig{
				CalcType:          "  sum  ",
				SummaryObject:     "  child__c  ",
				SummaryField:      "  amount__c  ",
				RelationshipField: "  parentid  ",
				Filter:            filter,
			},
		}},
	}
}

func TestPrepareTableDefinitionRollupSummaryResolvesSQLTypeAndConfig(t *testing.T) {
	filter := "Amount__c > 0"
	metaRepo := &rollupSchemaMetadataRepo{
		schemas: map[string]*models.ObjectMetadata{
			"child__c": testRollupChildSchema(),
		},
	}
	schemaRepo := &rollupSchemaDDLRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)
	schema := testRollupParentSchema(&filter)

	def, fields, err := ms.PrepareTableDefinition(context.Background(), schema)
	if err != nil {
		t.Fatalf("PrepareTableDefinition: %v", err)
	}

	col := metadataTestColumn(t, def.Columns, "Total__c")
	if col.Type != "DECIMAL(65,30)" {
		t.Fatalf("Total__c column type = %q, want DECIMAL(65,30)", col.Type)
	}
	if col.RollupConfig == nil {
		t.Fatal("Total__c column RollupConfig is nil")
	}
	if col.RollupConfig.CalcType != "SUM" || col.RollupConfig.SummaryObject != "Child__c" || col.RollupConfig.SummaryField != "Amount__c" || col.RollupConfig.RelationshipField != "ParentId" {
		t.Fatalf("Total__c column RollupConfig = %+v, want canonicalized config", col.RollupConfig)
	}
	if col.RollupConfig.Filter == nil || *col.RollupConfig.Filter != filter {
		t.Fatalf("Total__c column Filter = %#v, want %q", col.RollupConfig.Filter, filter)
	}
	if col.RollupConfig.ResultType == nil || *col.RollupConfig.ResultType != constants.FieldTypeCurrency || col.RollupConfig.ResultPrecision == nil || *col.RollupConfig.ResultPrecision != 18 || col.RollupConfig.ResultScale == nil || *col.RollupConfig.ResultScale != 2 {
		t.Fatalf("Total__c column result facets = %+v, want currency(18,2)", col.RollupConfig)
	}

	field := models.FindField(schema, "Total__c")
	if field == nil || field.RollupConfig == nil {
		t.Fatal("schema field RollupConfig missing after PrepareTableDefinition")
	}
	if col.RollupConfig == field.RollupConfig {
		t.Fatal("column RollupConfig reused schema field pointer")
	}
	if col.RollupConfig.Filter == field.RollupConfig.Filter {
		t.Fatal("column RollupConfig.Filter reused schema field pointer")
	}
	if field.ReturnType == nil || *field.ReturnType != constants.FieldTypeCurrency || field.Precision == nil || *field.Precision != 18 || field.Scale == nil || *field.Scale != 2 {
		t.Fatalf("Total__c field output metadata = %+v, want currency(18,2)", field)
	}

	for _, fc := range fields {
		if fc.Field == nil || fc.Field.APIName != "Total__c" {
			continue
		}
		if fc.Field.RollupConfig == nil || fc.Field.RollupConfig.CalcType != "SUM" {
			t.Fatalf("batch field RollupConfig = %+v, want canonicalized SUM config", fc.Field.RollupConfig)
		}
		return
	}
	t.Fatal("Total__c field missing from PrepareTableDefinition batch fields")
}

func TestCreateSchemaOptimizedRollupSummaryPassesCanonicalColumnDefinition(t *testing.T) {
	filter := "Amount__c > 0"
	metaRepo := &rollupSchemaMetadataRepo{
		schemas: map[string]*models.ObjectMetadata{
			"child__c": testRollupChildSchema(),
		},
	}
	schemaRepo := &rollupSchemaDDLRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)
	schema := testRollupParentSchema(&filter)

	if err := ms.CreateSchemaOptimized(context.Background(), schema); err != nil {
		t.Fatalf("CreateSchemaOptimized: %v", err)
	}
	if len(schemaRepo.created) != 1 {
		t.Fatalf("CreateTableWithStrictMetadata calls = %d, want 1", len(schemaRepo.created))
	}

	col := metadataTestColumn(t, schemaRepo.created[0].Columns, "Total__c")
	if col.Type != "DECIMAL(65,30)" {
		t.Fatalf("created Total__c column type = %q, want DECIMAL(65,30)", col.Type)
	}
	if col.RollupConfig == nil || col.RollupConfig.CalcType != "SUM" || col.RollupConfig.SummaryObject != "Child__c" || col.RollupConfig.SummaryField != "Amount__c" || col.RollupConfig.RelationshipField != "ParentId" {
		t.Fatalf("created Total__c RollupConfig = %+v, want canonicalized config", col.RollupConfig)
	}
}

func TestCreateSchemaRollupSummaryPassesCanonicalColumnDefinition(t *testing.T) {
	filter := "Amount__c > 0"
	metaRepo := &rollupSchemaMetadataRepo{
		schemas: map[string]*models.ObjectMetadata{
			"child__c": testRollupChildSchema(),
		},
	}
	schemaRepo := &rollupSchemaDDLRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)
	schema := testRollupParentSchema(&filter)

	if err := ms.CreateSchema(context.Background(), schema); err != nil {
		t.Fatalf("CreateSchema: %v", err)
	}
	if len(schemaRepo.created) != 1 {
		t.Fatalf("CreateTableWithStrictMetadata calls = %d, want 1", len(schemaRepo.created))
	}

	col := metadataTestColumn(t, schemaRepo.created[0].Columns, "Total__c")
	if col.Type != "DECIMAL(65,30)" {
		t.Fatalf("created Total__c column type = %q, want DECIMAL(65,30)", col.Type)
	}
	if col.RollupConfig == nil || col.RollupConfig.CalcType != "SUM" || col.RollupConfig.SummaryObject != "Child__c" || col.RollupConfig.SummaryField != "Amount__c" || col.RollupConfig.RelationshipField != "ParentId" {
		t.Fatalf("created Total__c RollupConfig = %+v, want canonicalized config", col.RollupConfig)
	}
}

func TestPrepareTableDefinitionRejectsRollupConfigOnNonRollupField(t *testing.T) {
	metaRepo := &rollupSchemaMetadataRepo{}
	schemaRepo := &rollupSchemaDDLRepo{}
	ms := NewMetadataService(metaRepo, NewSchemaManager(schemaRepo), nil)
	schema := &models.ObjectMetadata{
		APIName: "Parent__c",
		Label:   "Parent",
		Fields: []models.FieldMetadata{{
			APIName: "Name__c",
			Label:   "Name",
			Type:    constants.FieldTypeString,
			RollupConfig: &models.RollupConfig{
				CalcType:          "SUM",
				SummaryObject:     "Child__c",
				SummaryField:      "Amount__c",
				RelationshipField: "ParentId",
			},
		}},
	}

	if _, _, err := ms.PrepareTableDefinition(context.Background(), schema); err == nil {
		t.Fatal("PrepareTableDefinition accepted rollupConfig on non-rollup field, want rejection")
	} else if !strings.Contains(err.Error(), "rollupsummary") {
		t.Fatalf("error = %v, want rollupsummary diagnostic", err)
	}
}
