package analytics

import (
	"context"
	"strings"

	"github.com/nexuscrm/kernel/pkg/schemautil"
	"github.com/nexuscrm/shared/pkg/models"
)

// picklistResolver translates user-facing picklist filter values (Salesforce
// display labels) to the API values actually stored in the database.
//
// Context: report filters carry user-facing values like "Free Trial" but the
// DB may store the API value "free_trial". Without translation, WHERE clauses
// never match. Some DB fields do store labels ("Paid Advertising"), so the
// resolver is conservative: if a value matches neither a label nor a known
// option value, it is returned unchanged.
//
// See issue #476 for the originating bug report.
type picklistResolver struct {
	schemaLookup func(ctx context.Context, objectAPIName string) *models.ObjectMetadata
}

// newPicklistResolver builds a resolver backed by a schema lookup function.
// Passing nil for schemaLookup disables resolution entirely (all values pass
// through unchanged) — matches the existing ReportService pattern of
// tolerating a nil MetadataService.
func newPicklistResolver(schemaLookup func(ctx context.Context, objectAPIName string) *models.ObjectMetadata) *picklistResolver {
	return &picklistResolver{schemaLookup: schemaLookup}
}

// resolve returns the API value for a single user-facing filter value.
//
// Rules:
//   - If the field is not a picklist (or schema is unavailable) → return rawValue unchanged.
//   - If rawValue already matches an option's Value (case-insensitive) → return that Value
//     (canonicalized to the option's stored casing).
//   - If rawValue matches an option's Label (case-insensitive) → return that option's Value.
//   - Otherwise → return rawValue unchanged (conservative fallback: some DB fields
//     store labels verbatim, so we must not corrupt them).
//
// The resolver is idempotent: passing an already-resolved API value returns it unchanged.
func (r *picklistResolver) resolve(ctx context.Context, objectAPIName, fieldAPIName, rawValue string) string {
	if r == nil || r.schemaLookup == nil || objectAPIName == "" || fieldAPIName == "" || rawValue == "" {
		return rawValue
	}

	schema := r.schemaLookup(ctx, objectAPIName)
	if schema == nil {
		return rawValue
	}

	field := schemautil.FindField(schema, fieldAPIName)
	if field == nil || len(field.Options) == 0 {
		return rawValue
	}

	// Pass 1: exact or case-insensitive value match → canonicalize to stored value.
	for _, opt := range field.Options {
		if strings.EqualFold(opt.Value, rawValue) {
			return opt.Value
		}
	}

	// Pass 2: label match (case-insensitive) → return the corresponding value.
	for _, opt := range field.Options {
		if strings.EqualFold(opt.Label, rawValue) {
			return opt.Value
		}
	}

	// No match — leave as-is so fields that store labels verbatim keep working.
	return rawValue
}

// resolveMulti applies resolve() to a comma-separated value list. Each
// segment is trimmed, resolved independently, and the results are rejoined
// with ",". Used by the report filter `IN` / `NOT IN` path in buildCriteria().
func (r *picklistResolver) resolveMulti(ctx context.Context, objectAPIName, fieldAPIName, rawCSV string) string {
	if rawCSV == "" || !strings.Contains(rawCSV, ",") {
		return r.resolve(ctx, objectAPIName, fieldAPIName, rawCSV)
	}
	parts := strings.Split(rawCSV, ",")
	out := make([]string, len(parts))
	for i, p := range parts {
		out[i] = r.resolve(ctx, objectAPIName, fieldAPIName, strings.TrimSpace(p))
	}
	return strings.Join(out, ",")
}
