package querycodec

import (
	"fmt"
	"strings"
)

var toolingObjectAliases = map[string]string{
	"ValidationRule":    "_System_ValidationRule",
	"CustomApplication": "_System_App",
}

var toolingFieldAliases = map[string]string{
	"validationrule.EntityDefinition.DeveloperName": "ObjectApiName",
	"validationrule.ValidationName":                 "Name",
}

func translateToolingAliases(request *QueryRequest) string {
	if request == nil {
		return ""
	}
	toolingObject := request.ObjectAPIName
	objectLower := strings.ToLower(toolingObject)
	if alias, ok := toolingObjectAliases[toolingObject]; ok {
		request.ObjectAPIName = alias
	}
	for index, field := range request.SelectFields {
		request.SelectFields[index] = translateToolingSelection(objectLower, field)
	}
	request.GroupBy = translateToolingExpression(objectLower, request.GroupBy)
	request.SortField = translateToolingExpression(objectLower, request.SortField)
	for index := range request.OrderBy {
		request.OrderBy[index].Field = translateToolingExpression(
			objectLower,
			request.OrderBy[index].Field,
		)
	}
	request.FilterExpr = translateToolingExpression(objectLower, request.FilterExpr)
	request.Filter = translateToolingPredicate(objectLower, request.Filter)
	request.Having = translateToolingExpression(objectLower, request.Having)
	request.HavingPredicate = translateToolingPredicate(objectLower, request.HavingPredicate)
	for index := range request.SubQueries {
		request.SubQueries[index].FilterExpr = translateToolingExpression(objectLower, request.SubQueries[index].FilterExpr)
		request.SubQueries[index].Filter = translateToolingPredicate(objectLower, request.SubQueries[index].Filter)
	}
	for index := range request.SemiJoins {
		request.SemiJoins[index].OuterField = translateToolingField(
			objectLower,
			request.SemiJoins[index].OuterField,
		)
		translateToolingAliases(request.SemiJoins[index].InnerQuery)
	}
	return toolingObject
}

// Selection aliases are L1 response names, not source fields. The parser's
// deliberately narrow selection grammar normalizes both "AS alias" and
// "alias" to one space-delimited suffix, so lower only the source expression.
func translateToolingSelection(objectLower, selection string) string {
	expression, alias, found := strings.Cut(selection, " ")
	expression = translateToolingExpression(objectLower, expression)
	if !found {
		return expression
	}
	return expression + " " + alias
}

func translateToolingExpression(objectLower, expression string) string {
	for _, field := range []string{"EntityDefinition.DeveloperName", "ValidationName"} {
		expression = strings.ReplaceAll(expression, field, translateToolingField(objectLower, field))
	}
	return expression
}

func translateToolingField(objectLower, field string) string {
	if alias, ok := toolingFieldAliases[objectLower+"."+field]; ok {
		return alias
	}
	return field
}

func translateToolingPredicate(objectLower string, predicate Predicate) Predicate {
	switch predicate.Kind {
	case PredicateFieldComparison:
		predicate.Field = translateToolingField(objectLower, predicate.Field)
	case PredicateComparison:
		predicate.Left = translateToolingOperand(objectLower, predicate.Left)
		predicate.Right = translateToolingOperand(objectLower, predicate.Right)
	case PredicateExpression:
		predicate.Operand = translateToolingOperand(objectLower, predicate.Operand)
	case PredicateAnd, PredicateOr, PredicateNot:
		for index := range predicate.Children {
			predicate.Children[index] = translateToolingPredicate(objectLower, predicate.Children[index])
		}
	}
	return predicate
}

func translateToolingOperand(objectLower string, operand Operand) Operand {
	switch operand.Kind {
	case OperandField:
		operand.Field = translateToolingField(objectLower, operand.Field)
	case OperandFunction, OperandList:
		for index := range operand.Args {
			operand.Args[index] = translateToolingOperand(objectLower, operand.Args[index])
		}
		for index := range operand.Elements {
			operand.Elements[index] = translateToolingOperand(objectLower, operand.Elements[index])
		}
	}
	return operand
}

func projectToolingAttributes(response Response, version, toolingObject string, aggregate bool) error {
	if aggregate {
		return nil
	}
	body, ok := response.Body.(map[string]any)
	if !ok {
		return fmt.Errorf("sfcrm query codec: Tooling response is not a record envelope")
	}
	records, ok := body["records"].([]any)
	if !ok {
		return fmt.Errorf("sfcrm query codec: Tooling records are not a list")
	}
	for _, record := range records {
		row, ok := record.(map[string]any)
		if !ok {
			return fmt.Errorf("sfcrm query codec: Tooling record is not an object")
		}
		id, ok := row["Id"].(string)
		if !ok || id == "" {
			return errMissingRecordID
		}
		row["attributes"] = map[string]any{
			"type": toolingObject,
			"url":  fmt.Sprintf("/services/data/%s/tooling/sobjects/%s/%s", versionOrDefault(version), toolingObject, id),
		}
	}
	return nil
}
