package formula

import (
	"encoding/json"
	"fmt"
	"strings"
	"time"

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

type sharingRuleCriteriaJSONCriterion struct {
	Field     string          `json:"field"`
	Op        string          `json:"op"`
	Operation string          `json:"operation"`
	Value     json.RawMessage `json:"value"`
}

// SharingRuleCriteriaExpression normalizes the persisted SharingRule.Criteria
// surface. Formula text passes through unchanged; JSON array criteria are
// validated against object metadata before being rendered as a formula-shaped
// predicate for readers.
func SharingRuleCriteriaExpression(raw string, schema *models.ObjectMetadata) (string, error) {
	return sharingRuleCriteriaExpression(raw, schema, nil)
}

// SharingRuleRecordAccessCriteriaExpression is the one neutral criteria
// validator/normalizer shared by immutable Catalog compilation and the
// single-record Runtime access gate. Keeping it with the metadata model avoids
// making either Catalog or Runtime a competing criteria authority.
func SharingRuleRecordAccessCriteriaExpression(raw string, schema *models.ObjectMetadata) (string, error) {
	return sharingRuleCriteriaExpressionWithRenderer(raw, schema, sharingRuleRuntimeJSONField, sharingRuleRecordAccessCriterionExpression)
}

// SharingRuleRuntimeCriteriaExpression normalizes criteria for runtime readers
// that must stay query/record-access compatible.
func SharingRuleRuntimeCriteriaExpression(raw string, schema *models.ObjectMetadata) (string, error) {
	return sharingRuleCriteriaExpression(raw, schema, sharingRuleRuntimeJSONField)
}

type sharingRuleCriterionRenderer func(*models.FieldMetadata, string, string) (string, error)

func sharingRuleCriteriaExpression(raw string, schema *models.ObjectMetadata, fieldPolicy func(*models.FieldMetadata) error) (string, error) {
	return sharingRuleCriteriaExpressionWithRenderer(raw, schema, fieldPolicy, sharingRuleDefaultCriterionExpression)
}

func sharingRuleCriteriaExpressionWithRenderer(raw string, schema *models.ObjectMetadata, fieldPolicy func(*models.FieldMetadata) error, render sharingRuleCriterionRenderer) (string, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" || trimmed == "[]" {
		return "", nil
	}
	if !strings.HasPrefix(trimmed, "[") {
		return trimmed, nil
	}

	var criteria []sharingRuleCriteriaJSONCriterion
	if err := json.Unmarshal([]byte(trimmed), &criteria); err != nil {
		return "", fmt.Errorf("invalid sharing rule JSON criteria: %w", err)
	}
	if len(criteria) == 0 {
		return "", nil
	}

	parts := make([]string, 0, len(criteria))
	for _, criterion := range criteria {
		field, err := sharingRuleCriteriaJSONField(schema, criterion.Field)
		if err != nil {
			return "", err
		}
		if fieldPolicy != nil {
			if err := fieldPolicy(field); err != nil {
				return "", err
			}
		}
		op, err := sharingRuleCriteriaOperator(criterion)
		if err != nil {
			return "", err
		}
		literal, err := sharingRuleCriteriaLiteralForField(field, criterion.Value)
		if err != nil {
			return "", err
		}
		part, err := render(field, op, literal)
		if err != nil {
			return "", err
		}
		parts = append(parts, part)
	}
	return strings.Join(parts, " && "), nil
}

func sharingRuleDefaultCriterionExpression(field *models.FieldMetadata, op string, literal string) (string, error) {
	return fmt.Sprintf("%s %s %s", field.APIName, op, literal), nil
}

func sharingRuleRecordAccessCriterionExpression(field *models.FieldMetadata, op string, literal string) (string, error) {
	switch field.Type {
	case constants.FieldTypeDouble, constants.FieldTypeCurrency, constants.FieldTypePercent:
		if literal == "nil" {
			return sharingRuleDefaultCriterionExpression(field, op, literal)
		}
		switch op {
		case ">":
			return fmt.Sprintf("SAFEGT(%s, %s)", field.APIName, literal), nil
		case ">=":
			return fmt.Sprintf("SAFEGTE(%s, %s)", field.APIName, literal), nil
		case "<":
			return fmt.Sprintf("SAFELT(%s, %s)", field.APIName, literal), nil
		case "<=":
			return fmt.Sprintf("SAFELTE(%s, %s)", field.APIName, literal), nil
		case "==":
			return fmt.Sprintf("(SAFEGTE(%s, %s) && SAFELTE(%s, %s))", field.APIName, literal, field.APIName, literal), nil
		case "!=":
			return fmt.Sprintf("(SAFELT(%s, %s) || SAFEGT(%s, %s))", field.APIName, literal, field.APIName, literal), nil
		}
	}
	return sharingRuleDefaultCriterionExpression(field, op, literal)
}

func sharingRuleRuntimeJSONField(field *models.FieldMetadata) error {
	if field.IsSystem {
		return fmt.Errorf("sharing rule JSON criteria field %q is system-owned", field.APIName)
	}
	if field.Type == constants.FieldTypeRollupSummary {
		return fmt.Errorf("sharing rule JSON criteria field %q type %q requires rollup-summary parity coverage", field.APIName, field.Type)
	}
	return nil
}

func sharingRuleCriteriaJSONField(schema *models.ObjectMetadata, rawField string) (*models.FieldMetadata, error) {
	fieldName := strings.TrimSpace(rawField)
	if fieldName == "" {
		return nil, fmt.Errorf("sharing rule JSON criteria field is required")
	}
	if schema == nil {
		return nil, fmt.Errorf("sharing rule JSON criteria requires object metadata for field %q", fieldName)
	}
	field := models.FindField(schema, fieldName)
	if field == nil {
		return nil, fmt.Errorf("unknown sharing rule JSON criteria field %q", fieldName)
	}
	if field.IsVirtual {
		return nil, fmt.Errorf("sharing rule JSON criteria field %q is virtual", field.APIName)
	}
	if field.IsCompound {
		return nil, fmt.Errorf("sharing rule JSON criteria field %q is compound", field.APIName)
	}
	if field.ProjectionConfig != nil {
		return nil, fmt.Errorf("sharing rule JSON criteria field %q is projection-backed", field.APIName)
	}
	if field.Formula != nil || field.Type == constants.FieldTypeFormula || field.FormulaMode != nil || field.FormulaStorageMode != nil {
		return nil, fmt.Errorf("sharing rule JSON criteria field %q is formula-backed", field.APIName)
	}
	if field.Type == constants.FieldTypeDatetime {
		return nil, fmt.Errorf("sharing rule JSON criteria field %q type %q requires datetime parity coverage", field.APIName, field.Type)
	}
	return field, nil
}

func sharingRuleCriteriaLiteralForField(field *models.FieldMetadata, value json.RawMessage) (string, error) {
	switch field.Type {
	case constants.FieldTypeDate:
		return sharingRuleDateCriteriaLiteral(field, value)
	case constants.FieldTypeDouble, constants.FieldTypeCurrency, constants.FieldTypePercent:
		return sharingRuleNumericCriteriaLiteral(field, value)
	default:
		return sharingRuleCriteriaLiteral(value)
	}
}

func sharingRuleNumericCriteriaLiteral(field *models.FieldMetadata, value json.RawMessage) (string, error) {
	raw := strings.TrimSpace(string(value))
	if raw == "" || raw == "null" {
		return "nil", nil
	}

	dec := json.NewDecoder(strings.NewReader(raw))
	dec.UseNumber()
	var decoded interface{}
	if err := dec.Decode(&decoded); err != nil {
		return "", fmt.Errorf("invalid sharing rule JSON criteria value: %w", err)
	}
	number, ok := decoded.(json.Number)
	if !ok {
		return "", fmt.Errorf("sharing rule JSON criteria field %q type %q requires numeric or null literal", field.APIName, field.Type)
	}
	encoded, err := json.Marshal(number.String())
	if err != nil {
		return "", err
	}
	return string(encoded), nil
}

func sharingRuleDateCriteriaLiteral(field *models.FieldMetadata, value json.RawMessage) (string, error) {
	literal, err := sharingRuleStringCriteriaValue(field, value)
	if err != nil {
		return "", err
	}
	if _, err := time.Parse("2006-01-02", literal); err != nil {
		return "", fmt.Errorf("sharing rule JSON criteria field %q type %q requires YYYY-MM-DD literal", field.APIName, field.Type)
	}
	return sharingRuleStringLiteral(literal)
}

func sharingRuleStringCriteriaValue(field *models.FieldMetadata, value json.RawMessage) (string, error) {
	raw := strings.TrimSpace(string(value))
	dec := json.NewDecoder(strings.NewReader(raw))
	dec.UseNumber()
	var decoded interface{}
	if err := dec.Decode(&decoded); err != nil {
		return "", fmt.Errorf("invalid sharing rule JSON criteria value: %w", err)
	}
	literal, ok := decoded.(string)
	if !ok || strings.TrimSpace(literal) == "" {
		return "", fmt.Errorf("sharing rule JSON criteria field %q type %q requires string literal", field.APIName, field.Type)
	}
	return strings.TrimSpace(literal), nil
}

func sharingRuleCriteriaOperator(criterion sharingRuleCriteriaJSONCriterion) (string, error) {
	op := strings.TrimSpace(criterion.Op)
	if op == "" {
		op = strings.TrimSpace(criterion.Operation)
	}
	switch strings.ToLower(op) {
	case "=", "==", "equals", "equal", "eq":
		return "==", nil
	case "!=", "<>", "not_equal", "notequal", "notequals", "not equal", "ne":
		return "!=", nil
	case ">", "greaterthan", "greater_than", "gt":
		return ">", nil
	case ">=", "greaterorequal", "greater_or_equal", "greaterthanorequal", "greater_than_or_equal", "gte":
		return ">=", nil
	case "<", "lessthan", "less_than", "lt":
		return "<", nil
	case "<=", "lessorequal", "less_or_equal", "lessthanorequal", "less_than_or_equal", "lte":
		return "<=", nil
	default:
		return "", fmt.Errorf("unsupported sharing rule JSON criteria operator %q", op)
	}
}

func sharingRuleCriteriaLiteral(value json.RawMessage) (string, error) {
	raw := strings.TrimSpace(string(value))
	if raw == "" || raw == "null" {
		return "nil", nil
	}

	dec := json.NewDecoder(strings.NewReader(raw))
	dec.UseNumber()
	var decoded interface{}
	if err := dec.Decode(&decoded); err != nil {
		return "", fmt.Errorf("invalid sharing rule JSON criteria value: %w", err)
	}
	switch v := decoded.(type) {
	case nil:
		return "nil", nil
	case string:
		return sharingRuleStringLiteral(v)
	case bool:
		if v {
			return "true", nil
		}
		return "false", nil
	case json.Number:
		encoded, err := json.Marshal(v.String())
		if err != nil {
			return "", err
		}
		return string(encoded), nil
	default:
		return "", fmt.Errorf("unsupported sharing rule JSON criteria value %T", decoded)
	}
}

func sharingRuleStringLiteral(value string) (string, error) {
	encoded, err := json.Marshal(value)
	if err != nil {
		return "", err
	}
	return string(encoded), nil
}
