package dialectsfcrm

import (
	"errors"
	"fmt"
	"strings"

	"github.com/nexuscrm/shared/pkg/constants"
	"github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
)

var errMissingRecordID = errors.New("sfcrm: record has no Id field; cannot build attributes.url")

func recordAttributes(basePath, objectName string, row models.SObject, isAggregate bool) (map[string]any, error) {
	if isAggregate {
		return map[string]any{"type": "AggregateResult"}, nil
	}
	id, ok := row["Id"].(string)
	if !ok || id == "" {
		return nil, errMissingRecordID
	}
	return map[string]any{
		"type": objectName,
		"url":  fmt.Sprintf("%s/%s/%s", basePath, objectName, id),
	}, nil
}

func recordsToV63SObjects(records []contract.Record) []models.SObject {
	if records == nil {
		return nil
	}
	out := make([]models.SObject, 0, len(records))
	for _, record := range records {
		out = append(out, projectRecordToV63(record, v63RecordProjectionFull))
	}
	return out
}

type v63RecordProjectionMode int

const (
	v63RecordProjectionFull v63RecordProjectionMode = iota
	v63RecordProjectionFieldsOnly
	v63RecordProjectionFieldImage
)

func projectRecordToV63(record contract.Record, mode v63RecordProjectionMode) models.SObject {
	if record == nil {
		return nil
	}
	out := make(models.SObject, len(record))
	objectType := models.RecordObjectType(record)
	for key, value := range record {
		if models.IsRecordMetadataKey(key) {
			continue
		}
		out[key] = projectRecordValueToV63(value, nestedV63RecordProjectionMode(mode))
	}
	if mode == v63RecordProjectionFull && objectType != "" {
		out["attributes"] = map[string]interface{}{"type": objectType}
	}
	return out
}

func nestedV63RecordProjectionMode(mode v63RecordProjectionMode) v63RecordProjectionMode {
	if mode == v63RecordProjectionFieldImage {
		return v63RecordProjectionFull
	}
	return mode
}

func projectRecordValueToV63(value interface{}, mode v63RecordProjectionMode) interface{} {
	switch typed := value.(type) {
	case contract.Record:
		return projectRecordToV63(typed, mode)
	case models.SObject:
		return projectRecordToV63(contract.Record(typed), mode)
	case map[string]interface{}:
		if models.IsNeutralRecordMap(typed) {
			return projectRecordToV63(contract.Record(typed), mode)
		}
		out := make(map[string]interface{}, len(typed))
		for key, val := range typed {
			out[key] = projectRecordValueToV63(val, mode)
		}
		return out
	case []contract.Record:
		out := make([]models.SObject, 0, len(typed))
		for _, item := range typed {
			out = append(out, projectRecordToV63(item, mode))
		}
		return out
	case []models.SObject:
		out := make([]models.SObject, 0, len(typed))
		for _, item := range typed {
			out = append(out, projectRecordToV63(contract.Record(item), mode))
		}
		return out
	case []interface{}:
		out := make([]interface{}, 0, len(typed))
		for _, item := range typed {
			out = append(out, projectRecordValueToV63(item, mode))
		}
		return out
	default:
		return value
	}
}

func nestRelationshipFields(schema *models.ObjectMetadata, record models.SObject) {
	if schema == nil || record == nil {
		return
	}
	for _, f := range schema.Fields {
		if !constants.IsReferenceType(f.Type) || len(f.ReferenceTo) == 0 {
			continue
		}
		nameKey := f.APIName + "_Name"
		nameVal, hasName := record[nameKey]
		if !hasName {
			continue
		}
		relName := f.APIName
		if f.RelationshipName != nil && *f.RelationshipName != "" {
			relName = *f.RelationshipName
		} else if strings.HasSuffix(relName, "Id") {
			relName = strings.TrimSuffix(relName, "Id")
		}

		nested, _ := record[relName].(map[string]interface{})
		if nested == nil {
			nested = make(map[string]interface{})
		}
		if _, ok := nested["attributes"]; !ok {
			nested["attributes"] = map[string]interface{}{"type": f.ReferenceTo[0]}
		}
		if id, ok := record[f.APIName].(string); ok {
			nested["Id"] = id
		}
		if name, ok := nameVal.(string); ok {
			nested["Name"] = name
		}
		record[relName] = nested
	}
}
