package uiapi

import (
	"context"
	"fmt"
	"strconv"
	"strings"

	"github.com/nexuscrm/backend/internal/features/metadata"
	"github.com/nexuscrm/backend/internal/features/mru"
	"github.com/nexuscrm/backend/internal/features/permissions"
	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/fieldcapabilities"
	"github.com/nexuscrm/shared/pkg/models"
)

var uiCampaignHierarchyFields = []string{
	constants.FieldName,
	"ActualCost",
	"AmountAllOpportunities",
	"AmountWonOpportunities",
}

// isSystemAuditField returns true for fields that are always non-createable/non-updateable.
func isSystemAuditField(apiName string) bool {
	return apiName == constants.FieldID ||
		apiName == constants.FieldCreatedDate || apiName == constants.FieldCreatedByID ||
		apiName == constants.FieldLastModifiedDate || apiName == constants.FieldLastModifiedByID
}

// GetObjectInfo retrieves metadata for a specific object (Schema)
func (s *Service) GetObjectInfo(ctx context.Context, objectType string, user *models.UserSession) (*ObjectInfo, error) {
	// 1. Check Permissions
	if err := s.permSvc.CheckPermissionOrErrorWithUser(ctx, objectType, "Read", user); err != nil {
		return nil, err
	}

	// 2. Check Cache (per-user, since createable/updateable depend on permissions)
	userKey := ""
	if user != nil {
		userKey = user.ID
	}
	cacheKey := objectType + ":" + userKey
	if val, ok := s.objCache.get(cacheKey); ok {
		if cached, ok := val.(*ObjectInfo); ok {
			return cached, nil
		}
	}

	// 3. Retrieve and Transform
	resolver := permissions.NewFieldAccessResolver(s.permSvc)
	schema := s.metadataSvc.GetSchema(ctx, objectType)
	if schema == nil {
		return nil, nil
	}

	// Object-level permissions must come from the permission service so
	// system/system-metadata objects can still be writable when their schema
	// gates writes via RequiredWritePermission instead of ObjectPermissions rows.
	canCreate := s.permSvc.CheckObjectPermissionWithUser(ctx, objectType, constants.PermCreate, user)
	canUpdate := s.permSvc.CheckObjectPermissionWithUser(ctx, objectType, constants.PermEdit, user)
	canDelete := s.permSvc.CheckObjectPermissionWithUser(ctx, objectType, constants.PermDelete, user)

	objInfo := &ObjectInfo{
		ApiName:      schema.APIName,
		Label:        schema.Label,
		LabelPlural:  schema.PluralLabel,
		KeyPrefix:    mru.GetKeyPrefix(schema.APIName), // SSoT from MRU registry
		Queryable:    true,
		Createable:   canCreate,
		Updateable:   canUpdate,
		Deletable:    canDelete,
		Retrieveable: true,
		Searchable:   true,
		Triggerable:  !schema.IsSystem,
		Undeletable:  canDelete,
		Layoutable:   !schema.IsSystem,
		Fields:       make(map[string]FieldInfo),
	}

	for _, f := range schema.Fields {
		isRef := constants.IsReferenceType(f.Type)

		// Access projection: intrinsic + user permissions via resolver
		createable := resolver.EffectiveCreateable(ctx, &f, objectType, user)
		updateable := resolver.EffectiveUpdateable(ctx, &f, objectType, user)

		fi := FieldInfo{
			ApiName:    f.APIName,
			Label:      f.Label,
			DataType:   string(f.Type),
			Required:   f.Required,
			Createable: createable,
			Updateable: updateable,
			Reference:  isRef,
			NameField:  f.IsNameField,
		}

		// Populate reference metadata
		if isRef && len(f.ReferenceTo) > 0 {
			for _, refTo := range f.ReferenceTo {
				fi.ReferenceToInfos = append(fi.ReferenceToInfos, ReferenceToInfo{
					ApiName:    refTo,
					NameFields: []string{"Name"},
				})
			}
			if f.RelationshipName != nil {
				fi.RelationshipName = *f.RelationshipName
			}
		}

		objInfo.Fields[f.APIName] = fi
	}

	// 4. Populate Record Type Infos
	const masterID = "012000000000000AAA"
	rtInfos := map[string]UIRecordTypeInfo{
		masterID: {
			Name:                     "Master",
			RecordTypeId:             masterID,
			Available:                true,
			DefaultRecordTypeMapping: true,
			Master:                   true,
		},
	}
	defaultRTID := masterID

	dbRecordTypes := s.metadataSvc.GetRecordTypes(ctx, objectType)
	if len(dbRecordTypes) > 0 {
		// When real record types exist, Master is no longer the default
		rtInfos[masterID] = UIRecordTypeInfo{
			Name:                     "Master",
			RecordTypeId:             masterID,
			Available:                true,
			DefaultRecordTypeMapping: false,
			Master:                   true,
		}
		defaultAssigned := false
		for _, rt := range dbRecordTypes {
			if !rt.IsActive {
				continue
			}
			isDefault := !defaultAssigned
			rtInfos[rt.ID] = UIRecordTypeInfo{
				Name:                     rt.Name,
				RecordTypeId:             rt.ID,
				Available:                true,
				DefaultRecordTypeMapping: isDefault,
				Master:                   false,
			}
			if isDefault {
				defaultAssigned = true
				defaultRTID = rt.ID
			}
		}
	}
	objInfo.RecordTypeInfos = rtInfos
	objInfo.DefaultRecordTypeId = defaultRTID

	// 5. Update Cache
	s.objCache.set(cacheKey, objInfo)

	return objInfo, nil
}

// GetLayout retrieves the layout for a specific object and profile
func (s *Service) GetLayout(ctx context.Context, objectType string, user *models.UserSession) (*Layout, error) {
	return s.GetLayoutWithOptions(ctx, objectType, user, "", "")
}

// GetLayoutWithRecordType resolves a layout with full multi-dimensional context (Role + Profile + RecordType).
func (s *Service) GetLayoutWithRecordType(ctx context.Context, objectType string, user *models.UserSession, recordTypeID string) (*Layout, error) {
	return s.GetLayoutWithOptions(ctx, objectType, user, recordTypeID, "")
}

// GetLayoutWithOptions resolves a layout with full context including layout type (Create/Edit/Detail).
func (s *Service) GetLayoutWithOptions(ctx context.Context, objectType string, user *models.UserSession, recordTypeID string, layoutType string) (*Layout, error) {
	// 1. Check Permissions
	if err := s.permSvc.CheckPermissionOrErrorWithUser(ctx, objectType, "Read", user); err != nil {
		return nil, err
	}

	// 2. Compute role ancestors for resolution
	var roleAncestors []string
	profileID := ""
	roleKey := "norole"
	if user != nil {
		profileID = user.ProfileID
		if user.RoleID != nil {
			roleAncestors = append([]string{*user.RoleID}, s.permSvc.GetRoleAncestors(*user.RoleID)...)
			roleKey = *user.RoleID
		}
	}

	// 3. Generate Cache Key (Object:Profile:Role:RecordType:LayoutType)
	cacheKey := objectType + ":" + profileID + ":" + roleKey + ":" + recordTypeID + ":" + layoutType

	// 4. Check Cache
	if val, ok := s.layoutCache.get(cacheKey); ok {
		if cached, ok := val.(*Layout); ok {
			return cached, nil
		}
	}

	// 5. Resolve Layout via specificity-based assignment
	internalLayout := s.metadataSvc.ResolveLayout(ctx, objectType, roleAncestors, profileID, recordTypeID)
	if internalLayout == nil {
		return nil, nil
	}

	// 6. Map request type to response mode (resolution is mode-agnostic)
	mode := "View"
	switch layoutType {
	case "Create":
		mode = "Create"
	case "Edit":
		mode = "Edit"
	}

	// 7. Transform and cache
	uiLayout, err := s.transformLayout(ctx, internalLayout, objectType, mode)
	if err != nil {
		return nil, err
	}

	// 7. Update Cache
	s.layoutCache.set(cacheKey, uiLayout)

	return uiLayout, nil
}

// transformLayout converts an internal PageLayout to the Salesforce UI API Layout format.
// mode should be "View", "Edit", or "Create" — set by the caller based on request context.
func (s *Service) transformLayout(ctx context.Context, internalLayout *models.PageLayout, objectType string, mode string) (*Layout, error) {
	if mode == "" {
		mode = "View"
	}
	uiLayout := &Layout{
		Id:            internalLayout.ID,
		LayoutName:    internalLayout.LayoutName,
		ObjectApiName: objectType,
		LayoutType:    internalLayout.Type,
		Mode:          mode,
		CompactLayout: internalLayout.CompactLayout,
		Tabs:          internalLayout.Tabs,
		RelatedLists:  make([]RelatedListInfo, 0, len(internalLayout.RelatedLists)),
	}

	schema := s.metadataSvc.GetSchema(ctx, objectType)
	fieldIndex := make(map[string]*models.FieldMetadata)
	if schema != nil {
		for i := range schema.Fields {
			fieldIndex[schema.Fields[i].APIName] = &schema.Fields[i]
		}
	}

	for _, sec := range internalLayout.Sections {
		uiSec := LayoutSection{
			Id:          sec.ID,
			Heading:     sec.Label,
			Columns:     sec.Columns,
			UseHeading:  true,
			Collapsible: true,
			FieldLabels: sec.FieldLabels,
		}
		if sec.Tab != nil {
			uiSec.Tab = *sec.Tab
		}

		cols := sec.Columns
		if cols < 1 {
			cols = 1
		}

		var currentRow LayoutRow
		for i, fieldName := range sec.Fields {
			var item LayoutItem

			if dotIdx := strings.IndexByte(fieldName, '.'); dotIdx > 0 {
				parentFieldName := fieldName[:dotIdx]
				childFieldName := fieldName[dotIdx+1:]

				parentLabel := parentFieldName
				if fMeta, ok := fieldIndex[parentFieldName]; ok {
					parentLabel = fMeta.Label
				}

				label := parentLabel + " → " + childFieldName
				if customLabel, ok := sec.FieldLabels[fieldName]; ok && customLabel != "" {
					label = customLabel
				}

				isEditableExpansion := childFieldName == constants.FieldName

				details := map[string]interface{}{
					"lookupExpansion": true,
					"parentField":     parentFieldName,
					"displayField":    childFieldName,
				}
				if isEditableExpansion {
					details["editable"] = true
				}

				item = LayoutItem{
					Label: label,
					LayoutComponents: []LayoutComponent{
						{
							ComponentType: "Field",
							ApiName:       fieldName,
							Details:       details,
						},
					},
					EditableForNew:    isEditableExpansion,
					EditableForUpdate: isEditableExpansion,
				}
			} else {
				fieldLabel := fieldName
				required := false
				editableForNew := true
				editableForUpdate := true

				if fMeta, ok := fieldIndex[fieldName]; ok {
					fieldLabel = fMeta.Label
					required = fMeta.Required
					// Layout projection has no user session (no field-level
					// sharing to apply). Read intrinsic capability directly
					// from the live authority so Layer 1d/1e (discovered /
					// compound) take effect immediately, without a
					// BackfillFieldCapabilities pass. Issue #1582 RFC v1.5 PR D.
					editableForNew, editableForUpdate = fieldcapabilities.ComputeIntrinsic(objectType, fMeta)
				}

				if customLabel, ok := sec.FieldLabels[fieldName]; ok && customLabel != "" {
					fieldLabel = customLabel
				}

				item = LayoutItem{
					Label: fieldLabel,
					LayoutComponents: []LayoutComponent{
						{
							ComponentType: "Field",
							ApiName:       fieldName,
						},
					},
					Required:          required,
					EditableForNew:    editableForNew,
					EditableForUpdate: editableForUpdate,
				}
			}

			currentRow.LayoutItems = append(currentRow.LayoutItems, item)

			if len(currentRow.LayoutItems) == cols || i == len(sec.Fields)-1 {
				uiSec.LayoutRows = append(uiSec.LayoutRows, currentRow)
				currentRow = LayoutRow{}
			}
		}
		uiLayout.Sections = append(uiLayout.Sections, uiSec)
	}

	for _, rl := range internalLayout.RelatedLists {
		label := rl.Label
		fields := rl.Fields
		if isUICampaignHierarchyRelatedList(objectType, rl.ObjectAPIName, rl.LookupField) &&
			(len(fields) == 0 || label == "" || strings.EqualFold(label, "Campaigns")) {
			label = "Campaign Hierarchy"
			fields = append([]string(nil), uiCampaignHierarchyFields...)
		}

		uiLayout.RelatedLists = append(uiLayout.RelatedLists, RelatedListInfo{
			Id:              rl.ID,
			Label:           label,
			SecondaryLabel:  rl.SecondaryLabel,
			ObjectApiName:   rl.ObjectAPIName,
			LookupField:     rl.LookupField,
			Fields:          fields,
			PrefillMappings: rl.PrefillMappings,
			Resolution:      metadata.ResolveVirtualRelatedList(rl.ObjectAPIName, rl.LookupField),
		})
	}

	return uiLayout, nil
}

func isUICampaignHierarchyRelatedList(parentAPI string, childAPI string, lookupField string) bool {
	return strings.EqualFold(parentAPI, "Campaign") &&
		strings.EqualFold(childAPI, "Campaign") &&
		strings.EqualFold(lookupField, "ParentId")
}

// GetRecordDefaults returns default field values for creating a new record (Salesforce v63.0)
func (s *Service) GetRecordDefaults(ctx context.Context, objectType string, recordTypeId string, user *models.UserSession) (*RecordDefaults, error) {
	// 1. Check Permissions
	if err := s.permSvc.CheckPermissionOrErrorWithUser(ctx, objectType, "Create", user); err != nil {
		return nil, err
	}

	// 2. Get Schema
	schema := s.metadataSvc.GetSchema(ctx, objectType)
	if schema == nil {
		return nil, nil
	}

	// 3. Build default record with field defaults
	defaults := &RecordDefaults{
		ObjectApiName: objectType,
		RecordTypeId:  recordTypeId,
		Record: RecordWithDefaults{
			ApiName: objectType,
			Fields:  make(map[string]FieldValue),
		},
	}

	// 4. Get object info for enhanced response
	objInfo, err := s.GetObjectInfo(ctx, objectType, user)
	if err == nil && objInfo != nil {
		defaults.ObjectInfo = objInfo
	}

	// 5. Populate default values from schema
	for _, f := range schema.Fields {
		var defaultValue interface{}
		var displayValue *string

		// Only emit concrete defaults. Fields without explicit defaults should
		// remain null instead of receiving placeholder false/0/empty-string
		// values that the frontend cannot reliably distinguish from real defaults.
		if f.DefaultValue != nil {
			valStr := *f.DefaultValue
			defaultValue = valStr
			switch f.Type {
			case "boolean":
				if b, err := strconv.ParseBool(valStr); err == nil {
					defaultValue = b
				}
			case "number", "double", "currency", "percent":
				if parsed, err := strconv.ParseFloat(valStr, 64); err == nil {
					defaultValue = parsed
				}
			}
		}

		// Set owner to current user if applicable
		if f.APIName == "OwnerId" && user != nil {
			defaultValue = user.ID
			displayValue = &user.Name
		}

		defaults.Record.Fields[f.APIName] = FieldValue{
			Value:        defaultValue,
			DisplayValue: displayValue,
		}
	}

	return defaults, nil
}

// GetCloneDefaults returns pre-populated field values for cloning an existing record (Salesforce v63.0)
func (s *Service) GetCloneDefaults(ctx context.Context, recordId string, typeHint string, user *models.UserSession) (*RecordDefaults, error) {
	objectType, err := s.ResolveObjectType(ctx, recordId, typeHint, user)
	if err != nil {
		return nil, err
	}
	if objectType == "" {
		return nil, nil
	}

	sourceRecord, err := s.recordSvc.Retrieve(ctx, objectType, recordId, user)
	if err != nil {
		return nil, err
	}
	if sourceRecord == nil {
		return nil, nil
	}

	// 2. Check Permissions
	if err := s.permSvc.CheckPermissionOrErrorWithUser(ctx, objectType, "Create", user); err != nil {
		return nil, err
	}

	// 3. Get Schema
	schema := s.metadataSvc.GetSchema(ctx, objectType)
	if schema == nil {
		return nil, nil
	}

	// 4. Build clone defaults from source record
	defaults := &RecordDefaults{
		ObjectApiName: objectType,
		Record: RecordWithDefaults{
			ApiName: objectType,
			Fields:  make(map[string]FieldValue),
		},
	}

	// 5. Get object info
	objInfo, err := s.GetObjectInfo(ctx, objectType, user)
	if err == nil && objInfo != nil {
		defaults.ObjectInfo = objInfo
	}

	// 6. Copy values from source record (except Id and system fields)
	for _, f := range schema.Fields {
		// Skip Id and system-only fields for cloning (SSoT from constants)
		if isSystemAuditField(f.APIName) {
			continue
		}

		value := sourceRecord[f.APIName]
		var displayValue *string
		if value != nil {
			dv := valueToDisplayString(value)
			if dv != "" {
				displayValue = &dv
			}
		}

		// Override owner with current user
		if f.APIName == "OwnerId" && user != nil {
			value = user.ID
			displayValue = &user.Name
		}

		defaults.Record.Fields[f.APIName] = FieldValue{
			Value:        value,
			DisplayValue: displayValue,
		}
	}

	return defaults, nil
}

// GetListInfo returns list view metadata (Salesforce v63.0)
func (s *Service) GetListInfo(ctx context.Context, objectType string, listViewName string, user *models.UserSession) (*ListInfo, error) {
	// 1. Check Permissions
	if err := s.permSvc.CheckPermissionOrErrorWithUser(ctx, objectType, "Read", user); err != nil {
		return nil, err
	}

	// 2. Get list views from metadata
	listViews := s.metadataSvc.GetListViews(ctx, objectType)
	if listViews == nil {
		return nil, nil
	}

	// 3. Find matching list view
	var listView *models.ListView
	for i := range listViews {
		// Match by ID or Label (ListView doesn't have APIName)
		if listViews[i].ID == listViewName || listViews[i].Label == listViewName {
			listView = listViews[i]
			break
		}
	}

	if listView == nil {
		return nil, nil
	}

	// 4. Build ListInfo response
	info := &ListInfo{
		ApiName:       listView.ID, // Use ID as APIName
		Id:            listView.ID,
		Label:         listView.Label,
		DeveloperName: listView.ID,
		ObjectApiName: objectType,
		Visibility:    "Public",
	}

	// 5. Get schema for field labels — build index for O(1) lookup
	schema := s.metadataSvc.GetSchema(ctx, objectType)
	fieldLabels := make(map[string]string)
	if schema != nil {
		for _, f := range schema.Fields {
			fieldLabels[f.APIName] = f.Label
		}
	}

	// 6. Build display columns from Fields
	for _, col := range listView.Fields {
		label := col
		if l, ok := fieldLabels[col]; ok {
			label = l
		}
		info.DisplayColumns = append(info.DisplayColumns, DisplayColumn{
			FieldApiName: col,
			Label:        label,
			Sortable:     true,
		})
	}

	// Note: ListView model doesn't have SortBy/SortOrder - skip OrderedByInfo

	return info, nil
}

// valueToDisplayString converts interface{} to a display string for clone defaults
func valueToDisplayString(v interface{}) string {
	if v == nil {
		return ""
	}
	switch val := v.(type) {
	case string:
		return val
	case float64:
		return fmt.Sprintf("%g", val)
	case bool:
		if val {
			return "true"
		}
		return "false"
	default:
		return fmt.Sprintf("%v", val)
	}
}
