package metadata

import (
	"context"
	"fmt"
	"log"
	"strings"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/models"
)

// ResolvePicklistValuesForRecordType resolves the authoritative
// record-type-scoped value set for one object field.
func (ms *MetadataService) ResolvePicklistValuesForRecordType(
	ctx context.Context,
	objectAPIName string,
	recordTypeID string,
	fieldAPIName string,
) (*models.ScopedPicklistValueSet, error) {
	if ms == nil || ms.repo == nil {
		return nil, apperrors.NewInternalError("metadata repository is not configured", nil)
	}

	objectAPIName = strings.TrimSpace(objectAPIName)
	recordTypeID = strings.TrimSpace(recordTypeID)
	fieldAPIName = strings.TrimSpace(fieldAPIName)

	schema, err := ms.GetSchemaOrError(ctx, objectAPIName)
	if err != nil {
		return nil, err
	}
	field := findFieldByAPIName(schema, fieldAPIName)
	if field == nil {
		return nil, apperrors.NewNotFoundError("field", fmt.Sprintf("%s.%s", schema.APIName, fieldAPIName))
	}
	if !isPicklistLikeField(*field) {
		return nil, apperrors.NewValidationError(field.APIName, "field is not a picklist or multipicklist")
	}

	if recordTypeID == "" || recordTypeID == constants.SyntheticMasterRecordTypeID {
		return masterPicklistValueSet(schema.APIName, constants.SyntheticMasterRecordTypeID, *field), nil
	}
	if field.ID == "" {
		return nil, apperrors.NewInternalError(fmt.Sprintf("field %s.%s has no metadata id", schema.APIName, field.APIName), nil)
	}

	recordTypes, err := ms.repo.GetRecordTypes(ctx, schema.APIName)
	if err != nil {
		return nil, apperrors.NewInternalError(fmt.Sprintf("failed to query record types for %s", schema.APIName), err)
	}
	recordType := findActiveRecordType(recordTypes, schema.APIName, recordTypeID)
	if recordType == nil {
		return nil, apperrors.NewNotFoundError("record type", fmt.Sprintf("%s on object %s", recordTypeID, schema.APIName))
	}

	scopedRows, err := ms.repo.GetScopedPicklistValues(ctx, recordType.ID, field.ID)
	if err != nil {
		return nil, err
	}
	if len(scopedRows) == 0 {
		return nil, apperrors.NewNotFoundError(
			"scoped picklist values",
			fmt.Sprintf("record type %s on field %s.%s", recordType.ID, schema.APIName, field.APIName),
		)
	}

	catalogue := indexPicklistCatalogue(field.Options)
	values := make([]models.ScopedPicklistValue, 0, len(scopedRows))
	for _, row := range scopedRows {
		option, ok := catalogue[row.Value]
		if !ok {
			return nil, apperrors.NewInternalError(
				fmt.Sprintf("scoped picklist value %q for record type %s on field %s.%s is not in field catalogue", row.Value, recordType.ID, schema.APIName, field.APIName),
				nil,
			)
		}
		row.Label = option.Label
		if row.Label == "" {
			row.Label = row.Value
		}
		values = append(values, row)
	}
	defaultValue := normalizeScopedPicklistDefaults(values, schema.APIName, field.APIName, recordType.ID)

	return &models.ScopedPicklistValueSet{
		ObjectAPIName:      schema.APIName,
		FieldAPIName:       field.APIName,
		RecordTypeID:       recordType.ID,
		IsMaster:           false,
		Values:             values,
		DefaultValue:       defaultValue,
		BusinessProcessID:  copyStringPtr(recordType.BusinessProcessID),
		ControllingField:   copyStringPtr(field.ControllingField),
		PicklistDependency: cloneStringSliceMap(field.PicklistDependency),
	}, nil
}

func findFieldByAPIName(schema *models.ObjectMetadata, fieldAPIName string) *models.FieldMetadata {
	if schema == nil {
		return nil
	}
	for i := range schema.Fields {
		if strings.EqualFold(schema.Fields[i].APIName, fieldAPIName) {
			return &schema.Fields[i]
		}
	}
	return nil
}

func isPicklistLikeField(field models.FieldMetadata) bool {
	normalized := constants.NormalizeFieldType(string(field.Type))
	return normalized == constants.FieldTypePicklist || normalized == constants.FieldTypeMultiPicklist
}

func masterPicklistValueSet(objectAPIName, recordTypeID string, field models.FieldMetadata) *models.ScopedPicklistValueSet {
	values := make([]models.ScopedPicklistValue, 0, len(field.Options))
	for i, option := range field.Options {
		label := option.Label
		if label == "" {
			label = option.Value
		}
		isDefault := field.DefaultValue != nil && *field.DefaultValue == option.Value
		values = append(values, models.ScopedPicklistValue{
			Value:     option.Value,
			Label:     label,
			IsActive:  true,
			IsDefault: isDefault,
			SortOrder: i,
		})
	}
	defaultValue := copyStringPtr(field.DefaultValue)
	return &models.ScopedPicklistValueSet{
		ObjectAPIName:      objectAPIName,
		FieldAPIName:       field.APIName,
		RecordTypeID:       recordTypeID,
		IsMaster:           true,
		Values:             values,
		DefaultValue:       defaultValue,
		ControllingField:   copyStringPtr(field.ControllingField),
		PicklistDependency: cloneStringSliceMap(field.PicklistDependency),
	}
}

func findActiveRecordType(recordTypes []*models.RecordType, objectAPIName, recordTypeID string) *models.RecordType {
	for _, rt := range recordTypes {
		if rt == nil || !strings.EqualFold(rt.ID, recordTypeID) {
			continue
		}
		if !rt.IsActive || !strings.EqualFold(rt.ObjectAPIName, objectAPIName) {
			return nil
		}
		return rt
	}
	return nil
}

func indexPicklistCatalogue(options models.PicklistOptions) map[string]models.PicklistOption {
	catalogue := make(map[string]models.PicklistOption, len(options))
	for _, option := range options {
		catalogue[option.Value] = option
	}
	return catalogue
}

// normalizeScopedPicklistDefaults selects the published default among the
// IsDefault-flagged rows. Inactive rows are excluded from default selection
// (Codex round-4 HIGH on PR #919): an `IsActive=0, IsDefault=1` row is
// internally inconsistent and would prepopulate an unusable default for record
// creation. We log a warning when defaults exist but all are inactive, and
// also strip the IsDefault flag from any inactive row in the projected
// `values` so the wire shape never reports `default=true` on an unavailable
// option. Layer 1 (manifest validation in record_type_picklist_value.go)
// fails closed at install; this read-path normalization defends against any
// pre-existing or out-of-band drift in live `_System_RecordTypePicklistValue`
// rows.
func normalizeScopedPicklistDefaults(values []models.ScopedPicklistValue, objectAPIName, fieldAPIName, recordTypeID string) *string {
	best := -1
	flaggedDefaults := 0
	for i := range values {
		if !values[i].IsDefault {
			continue
		}
		flaggedDefaults++
		if !values[i].IsActive {
			continue
		}
		if best < 0 || scopedDefaultWins(values[i], values[best]) {
			best = i
		}
	}
	for i := range values {
		values[i].IsDefault = i == best
	}
	if best < 0 {
		if flaggedDefaults > 0 {
			log.Printf("⚠️ scoped picklist default suppressed: all %d default-flagged rows are inactive for %s.%s on record type %s",
				flaggedDefaults, objectAPIName, fieldAPIName, recordTypeID)
		}
		return nil
	}
	value := values[best].Value
	return &value
}

func scopedDefaultWins(candidate, incumbent models.ScopedPicklistValue) bool {
	candidateRank := scopedDefaultRank(candidate)
	incumbentRank := scopedDefaultRank(incumbent)
	if candidateRank != incumbentRank {
		return candidateRank > incumbentRank
	}
	if candidate.SortOrder != incumbent.SortOrder {
		return candidate.SortOrder < incumbent.SortOrder
	}
	return candidate.Value < incumbent.Value
}

func scopedDefaultRank(value models.ScopedPicklistValue) int {
	if value.IsCustomized {
		return 2
	}
	if strings.TrimSpace(value.OwnerPack) != "" {
		return 1
	}
	return 0
}

func copyStringPtr(value *string) *string {
	if value == nil {
		return nil
	}
	out := *value
	return &out
}

func cloneStringSliceMap(in map[string][]string) map[string][]string {
	if len(in) == 0 {
		return nil
	}
	out := make(map[string][]string, len(in))
	for key, values := range in {
		out[key] = append([]string(nil), values...)
	}
	return out
}
