package models

import (
	"database/sql"
	"reflect"

	"github.com/nexuscrm/shared/pkg/queryspec"
	contractmodels "github.com/pingcap-inc/nexuscrm-contract/pkg/models"
)

// SObject represents a generic record
type SObject = contractmodels.SObject

// Record is the application-neutral name for the generic record carrier.
// SObject remains as a compatibility alias for existing engine code; new
// neutral capability paths should use Record so product dialect vocabulary does
// not spread through L0 APIs.
type Record = contractmodels.SObject

const ObjectTypeMetadataKey = "_objectType"
const ExactDecimalMetadataKey = "_exactDecimals"

// IsRecordMetadataKey reports whether key is an internal record metadata carrier
// rather than a user-authored field.
func IsRecordMetadataKey(key string) bool {
	return key == ObjectTypeMetadataKey || key == ExactDecimalMetadataKey || key == "attributes"
}

// RecordObjectType reads the neutral object-type metadata off an SObject or
// plain map. It still accepts Salesforce attributes.type as a compatibility
// fallback for v63 ingress and legacy in-engine carriers. Returns "" when
// unavailable.
func RecordObjectType(record interface{}) string {
	if rawType, ok := recordValue(record, ObjectTypeMetadataKey); ok {
		if typeName, _ := rawType.(string); typeName != "" {
			return typeName
		}
	}

	attrs, ok := recordValue(record, "attributes")
	if !ok {
		return ""
	}
	attrMap, ok := attrs.(map[string]interface{})
	if !ok {
		return ""
	}
	typeName, _ := attrMap["type"].(string)
	return typeName
}

// IsNeutralRecordMap reports whether an untyped map is a neutral record (carries
// the authoritative _objectType marker) rather than a plain JSON/value map. This
// is the single discriminator neutral adapters MUST use when deciding whether to
// re-project or convert an untyped map *value* as a record.
//
// Do NOT use RecordObjectType for that decision: its attributes.type compatibility
// fallback misclassifies ordinary JSON values such as
// {"attributes":{"type":"x","source":"y"}} as records, corrupting them. Genuine
// nested records are always stamped with _objectType (StampObjectType) before they
// reach an adapter, so _objectType presence is the reliable, non-heuristic signal.
func IsNeutralRecordMap(m map[string]interface{}) bool {
	objType, ok := m[ObjectTypeMetadataKey].(string)
	return ok && objType != ""
}

func recordValue(record interface{}, key string) (interface{}, bool) {
	switch obj := record.(type) {
	case SObject:
		value, ok := obj[key]
		return value, ok
	case map[string]interface{}:
		value, ok := obj[key]
		return value, ok
	}

	rv := reflect.ValueOf(record)
	if !rv.IsValid() || rv.Kind() != reflect.Map || rv.Type().Key().Kind() != reflect.String {
		return nil, false
	}
	keyValue := reflect.ValueOf(key)
	if !keyValue.Type().AssignableTo(rv.Type().Key()) {
		if !keyValue.Type().ConvertibleTo(rv.Type().Key()) {
			return nil, false
		}
		keyValue = keyValue.Convert(rv.Type().Key())
	}
	value := rv.MapIndex(keyValue)
	if !value.IsValid() {
		return nil, false
	}
	return value.Interface(), true
}

// StampObjectType writes the object's API name into neutral record metadata in
// place, only when absent (caller-provided values win). No-op on nil record or
// empty objectType.
func StampObjectType(record SObject, objectType string) {
	if record == nil || objectType == "" {
		return
	}
	if _, present := record[ObjectTypeMetadataKey]; present {
		return
	}
	if existing, _ := record["attributes"].(map[string]interface{}); existing != nil {
		if value, present := existing["type"]; present {
			if existingType, _ := value.(string); existingType != "" {
				record[ObjectTypeMetadataKey] = existingType
			}
			return
		}
	}
	record[ObjectTypeMetadataKey] = objectType
}

// SearchResult represents global search results
type SearchResult struct {
	ObjectLabel   string
	ObjectAPIName string
	Icon          string
	Matches       []SObject
}

// AnalyticsAggregate represents one aggregate expression in an analytics query.
type AnalyticsAggregate struct {
	Type  string
	Field string
}

// AnalyticsQuery represents an analytics query
type AnalyticsQuery struct {
	ObjectAPIName     string
	Operation         string // count, sum, avg, group_by
	Field             *string
	AggregateType     string // SUM, AVG, MIN, MAX (for group_by)
	Aggregates        []AnalyticsAggregate
	GroupBy           *string
	Having            string           // Legacy executor HAVING expression after neutral lowering
	FilterExpr        string           // Legacy executor filter expression after neutral lowering
	Criteria          []QueryCriterion // Structured criteria (Preferred)
	SortField         string
	SortDirection     string
	Limit             int // Optional limit for group_by (default 500)
	Offset            int
	ScanAll           bool
	ParentFilterPaths []QueryParentFilterPath // service-validated execution plans for parent FilterExpr/Criteria/GroupBy paths
}

// QueryCriterion represents a single query filter criterion
type QueryCriterion struct {
	Field string
	Op    string
	Val   interface{}
}

// SortCriterion represents a sorting condition
type SortCriterion struct {
	Field     string
	Direction string
}

// SubQueryRequest represents a child relationship subquery (e.g., SELECT Id FROM Contacts)
type SubQueryRequest struct {
	RelationshipName string
	SelectFields     []string
	Filter           queryspec.Predicate
	FilterExpr       string
	Criteria         []QueryCriterion
	OrderBy          []SortCriterion
	Limit            int
}

// SemiJoinClause carries a parsed `field IN (SELECT ...)` membership predicate
// for dialects that preserve subquery structure before neutral lowering.
// Production read paths lower this shape into queryspec.CorrelatedMembership
// instead of materializing the inner result set into the outer predicate.
//
// Index identifies the clause within QueryRequest.SemiJoins for dialect-local
// parser bookkeeping; engine consumers must not interpret it as a runtime fact.
type SemiJoinClause struct {
	OuterField string        // LHS field name from `outerField IN (SELECT ...)`
	Negate     bool          // true for NOT IN
	InnerQuery *QueryRequest // parsed inner membership query
	Index      int           // dialect-local clause index
}

// QueryParentFilterTargetRecordVisibility is a structured row-read visibility
// arm for parent targets whose rows reference another target record. QueryService
// owns metadata/RLS validation; repositories only consume physical columns.
type QueryParentFilterTargetRecordVisibility struct {
	ReferencedObjectAPIName                                   string
	ObjectFieldPhysicalName                                   string
	RecordFieldPhysicalName                                   string
	ReferencedTable                                           string
	ReferencedIDPhysicalName                                  string
	ReferencedHasIsDeleted                                    bool
	ReferencedIncludeDeleted                                  bool
	ReferencedIsDeletedPhysicalName                           string
	ReferencedVisibilityDenyAll                               bool
	ReferencedVisibilityAllowedIDPhysicalName                 string
	ReferencedVisibilityAllowedIDs                            []string
	ReferencedVisibilityOwnerPhysicalName                     string
	ReferencedVisibilityOwnerUserIDs                          []string
	ReferencedVisibilityGroupOwnerIDs                         []string
	ReferencedVisibilityManualShareUserID                     string
	ReferencedVisibilityTeamMemberUserID                      string
	ReferencedVisibilitySharingRuleOwnerUserIDs               []string
	ReferencedVisibilitySharingRuleFromGroupIDs               []string
	ReferencedVisibilityOwnerVisibilityOwnerPhysicalName      string
	ReferencedVisibilityOwnerVisibilityVisibilityPhysicalName string
	ReferencedVisibilityOwnerVisibilityUserID                 string
	ReferencedVisibilityOwnerVisibilityOwnerValues            []string
	ReferencedVisibilityOwnerVisibilityPublicValues           []string
	ReferencedVisibilityCriteriaExpressions                   []string
	ReferencedVisibilityCriteriaFields                        []QueryParentFilterCriteriaField
	ReferencedVisibilityTargetRecordFilters                   []QueryParentFilterTargetRecordVisibility
	// Alternatives uses nil for the legacy flat plan. A non-nil slice is a
	// grouped association plan; an empty group is a fail-closed association
	// whose record field may only be absent.
	Alternatives []QueryParentFilterTargetRecordVisibility
}

// TargetRecordVisibilityAlternatives normalizes the legacy flat plan into the
// grouped association plan consumed by repositories.
func (f QueryParentFilterTargetRecordVisibility) TargetRecordVisibilityAlternatives() []QueryParentFilterTargetRecordVisibility {
	if f.Alternatives != nil {
		return f.Alternatives
	}
	if f.ReferencedObjectAPIName == "" && f.ReferencedTable == "" {
		return nil
	}
	return []QueryParentFilterTargetRecordVisibility{f}
}

// QueryParentFilterHop is one resolved relationship edge in a validated parent
// field predicate. QueryService owns metadata/FLS validation and fills this
// plan; repositories only consume it.
type QueryParentFilterHop struct {
	RelationshipName                                      string
	SourceObjectAPIName                                   string
	ForeignKeyField                                       string
	ForeignKeyPhysicalName                                string
	TargetObjectAPIName                                   string
	TargetTable                                           string
	TargetIDPhysicalName                                  string
	TargetHasIsDeleted                                    bool
	TargetIsDeletedPhysicalName                           string
	TargetVisibilityDenyAll                               bool
	TargetVisibilityAllowedIDPhysicalName                 string
	TargetVisibilityAllowedIDs                            []string
	TargetVisibilityOwnerPhysicalName                     string
	TargetVisibilityOwnerUserIDs                          []string
	TargetVisibilityGroupOwnerIDs                         []string
	TargetVisibilityManualShareUserID                     string
	TargetVisibilityTeamMemberUserID                      string
	TargetVisibilitySharingRuleOwnerUserIDs               []string
	TargetVisibilitySharingRuleFromGroupIDs               []string
	TargetVisibilityOwnerVisibilityOwnerPhysicalName      string
	TargetVisibilityOwnerVisibilityVisibilityPhysicalName string
	TargetVisibilityOwnerVisibilityUserID                 string
	TargetVisibilityOwnerVisibilityOwnerValues            []string
	TargetVisibilityOwnerVisibilityPublicValues           []string
	TargetVisibilityCriteriaExpressions                   []string
	TargetVisibilityCriteriaFields                        []QueryParentFilterCriteriaField
	TargetVisibilityTargetRecordFilters                   []QueryParentFilterTargetRecordVisibility
}

// QueryParentFilterCriteriaField is a validated physical field dependency for
// parent target criteria visibility. Expressions stay metadata-authored while
// repositories receive the physical read path they are allowed to map.
type QueryParentFilterCriteriaField struct {
	APIName      string
	PhysicalName string
}

// QueryParentFilterPath is the repository execution plan for a validated
// parent field predicate in QueryRequest.FilterExpr. QueryService owns
// metadata/FLS validation and fills this plan; repositories only consume it.
type QueryParentFilterPath struct {
	RawPath                                               string
	RelationshipHops                                      []QueryParentFilterHop
	RelationshipName                                      string
	RootForeignKeyField                                   string
	RootForeignKeyPhysicalName                            string
	TargetObjectAPIName                                   string
	TargetTable                                           string
	TargetIDPhysicalName                                  string
	TargetFieldAPIName                                    string
	TargetFieldPhysicalName                               string
	TargetFieldCompoundMemberPhysicalNames                []string
	TargetHasIsDeleted                                    bool
	TargetIsDeletedPhysicalName                           string
	TargetVisibilityDenyAll                               bool
	TargetVisibilityAllowedIDPhysicalName                 string
	TargetVisibilityAllowedIDs                            []string
	TargetVisibilityOwnerPhysicalName                     string
	TargetVisibilityOwnerUserIDs                          []string
	TargetVisibilityGroupOwnerIDs                         []string
	TargetVisibilityManualShareUserID                     string
	TargetVisibilityTeamMemberUserID                      string
	TargetVisibilitySharingRuleOwnerUserIDs               []string
	TargetVisibilitySharingRuleFromGroupIDs               []string
	TargetVisibilityOwnerVisibilityOwnerPhysicalName      string
	TargetVisibilityOwnerVisibilityVisibilityPhysicalName string
	TargetVisibilityOwnerVisibilityUserID                 string
	TargetVisibilityOwnerVisibilityOwnerValues            []string
	TargetVisibilityOwnerVisibilityPublicValues           []string
	TargetVisibilityCriteriaExpressions                   []string
	TargetVisibilityCriteriaFields                        []QueryParentFilterCriteriaField
	TargetVisibilityTargetRecordFilters                   []QueryParentFilterTargetRecordVisibility
}

// QueryRequest represents the internal normalized query request used by the
// query parser, service, and repository. External wire requests use
// shared/pkg/api DTOs or dialect-local binders instead.
type QueryRequest struct {
	ObjectAPIName     string
	Criteria          []QueryCriterion
	Filter            queryspec.Predicate
	FilterExpr        string // Legacy executor filter expression after neutral lowering
	SortField         string
	SortDirection     string
	Limit             int
	Offset            int
	SkipTotalSize     bool // Internal-only fast path for callers that do not need exact totalSize.
	ScanAll           bool
	OrderBy           []SortCriterion
	SelectFields      []string
	MRUUserID         string // When set, JOIN _System_UserRecentItem and ORDER BY AccessedAt DESC
	GroupBy           string
	HavingPredicate   queryspec.Predicate
	Having            string
	SubQueries        []SubQueryRequest
	SemiJoins         []SemiJoinClause        // resolved server-side; not part of wire contract
	ParentFilterPaths []QueryParentFilterPath // service-validated execution plans for parent FilterExpr paths
}

// QueryResult represents the result of a query execution.
//
// IsAggregate is the authoritative signal that the records in this result are
// AggregateResult rows — produced by either an aggregate function in SELECT
// (COUNT/SUM/AVG/MIN/MAX) or a GROUP BY clause. REST/Tooling response
// formatting layers must consult this flag to set Salesforce-compatible
// `attributes.type == "AggregateResult"` (with no `url`) instead of inferring
// aggregate-ness from row contents (which fails for `GROUP BY Id` and similar
// queries where Id is on the row even though the result is an aggregate).
type QueryResult struct {
	TotalSize        int
	TotalSizeUnknown bool
	Done             bool
	Records          []SObject
	IsAggregate      bool
}

// Transaction represents a database transaction
type Transaction interface {
	Exec(query string, args ...interface{}) (sql.Result, error)
	Query(query string, args ...interface{}) (*sql.Rows, error)
	QueryRow(query string, args ...interface{}) *sql.Row
	Commit() error
	Rollback() error
}
