package integration

import (
	"strings"

	"github.com/warmbly/warmbly/internal/models"
)

// project.go turns a Warmbly source record into provider destination properties
// using the connection's configured field map — this is the pivot that makes the
// CRM handlers config-driven instead of hardcoding props["firstname"]. When no
// map is configured the provider's default identity map is used, so an
// unconfigured connection behaves exactly as it did before the framework.

// projectFields applies a resolved field map to a source payload, producing
// {externalField: value}. Empty values are dropped (matching provider upsert
// semantics: don't overwrite with blanks).
func projectFields(entries []models.FieldMapEntry, source map[string]any) map[string]any {
	out := map[string]any{}
	for _, e := range entries {
		var val string
		if e.Transform == models.FieldTransformStatic {
			// Static values support {{.key}} templating against the event data.
			val = renderTemplate(e.StaticValue, source)
		} else {
			val = applyTransform(e.Transform, warmblyValue(source, e.WarmblyField))
		}
		if strings.TrimSpace(val) == "" {
			continue
		}
		out[e.ExternalField] = val
	}
	return out
}

// warmblyValue reads a source field. "custom:<key>" reads from the contact's
// custom_fields map; everything else is a top-level normalized field.
func warmblyValue(source map[string]any, field string) string {
	if strings.HasPrefix(field, "custom:") {
		key := strings.TrimPrefix(field, "custom:")
		switch cf := source["custom_fields"].(type) {
		case map[string]string:
			return strings.TrimSpace(cf[key])
		case map[string]any:
			if v, ok := cf[key].(string); ok {
				return strings.TrimSpace(v)
			}
		}
		return ""
	}
	return stringFromMap(source, field)
}

func applyTransform(t models.FieldTransform, v string) string {
	switch t {
	case models.FieldTransformUppercase:
		return strings.ToUpper(v)
	case models.FieldTransformLowercase:
		return strings.ToLower(v)
	case models.FieldTransformTrim:
		return strings.TrimSpace(v)
	default:
		return v
	}
}

// defaultObject is the CRM object an upsert action writes for a provider.
func defaultObject(provider models.IntegrationProvider) string {
	switch provider {
	case models.IntegrationPipedrive:
		return "person"
	case models.IntegrationClose:
		return "lead"
	default:
		return "contact"
	}
}

// defaultFieldMap is the provider's identity projection — the standard fields the
// handlers used to hardcode. Configured mappings overlay this.
func defaultFieldMap(provider models.IntegrationProvider) []models.FieldMapEntry {
	switch provider {
	case models.IntegrationHubSpot:
		return []models.FieldMapEntry{
			{WarmblyField: "email", ExternalField: "email"},
			{WarmblyField: "first_name", ExternalField: "firstname"},
			{WarmblyField: "last_name", ExternalField: "lastname"},
			{WarmblyField: "company", ExternalField: "company"},
			{WarmblyField: "phone", ExternalField: "phone"},
		}
	case models.IntegrationSalesforce:
		return []models.FieldMapEntry{
			{WarmblyField: "email", ExternalField: "Email"},
			{WarmblyField: "first_name", ExternalField: "FirstName"},
			{WarmblyField: "last_name", ExternalField: "LastName"},
			{WarmblyField: "phone", ExternalField: "Phone"},
		}
	case models.IntegrationPipedrive:
		return []models.FieldMapEntry{
			{WarmblyField: "name", ExternalField: "name"},
			{WarmblyField: "email", ExternalField: "email"},
			{WarmblyField: "phone", ExternalField: "phone"},
		}
	case models.IntegrationClose:
		return []models.FieldMapEntry{
			{WarmblyField: "name", ExternalField: "name"},
			{WarmblyField: "email", ExternalField: "email"},
			{WarmblyField: "phone", ExternalField: "phone"},
			{WarmblyField: "company", ExternalField: "company"},
		}
	}
	return nil
}

// effectiveFieldMap resolves the final field map for an object with precedence:
// provider defaults < connection-default rows (subscription_id NULL) <
// subscription-scoped rows < inline subscription config. Later entries override
// earlier ones keyed by destination field; original order is preserved.
func effectiveFieldMap(provider models.IntegrationProvider, object string, rows []models.IntegrationFieldMapping, subID string, inline []models.FieldMapEntry) []models.FieldMapEntry {
	byExt := map[string]models.FieldMapEntry{}
	order := []string{}
	add := func(e models.FieldMapEntry) {
		if e.ExternalField == "" {
			return
		}
		if _, seen := byExt[e.ExternalField]; !seen {
			order = append(order, e.ExternalField)
		}
		byExt[e.ExternalField] = e
	}

	for _, e := range defaultFieldMap(provider) {
		add(e)
	}
	rowToEntry := func(r models.IntegrationFieldMapping) models.FieldMapEntry {
		return models.FieldMapEntry{
			WarmblyField:  r.WarmblyField,
			ExternalField: r.ExternalField,
			Transform:     models.FieldTransform(r.Transform),
			StaticValue:   r.StaticValue,
		}
	}
	for _, r := range rows { // connection defaults
		if r.ObjectName == object && r.SubscriptionID == nil {
			add(rowToEntry(r))
		}
	}
	if subID != "" {
		for _, r := range rows { // subscription-scoped
			if r.ObjectName == object && r.SubscriptionID != nil && r.SubscriptionID.String() == subID {
				add(rowToEntry(r))
			}
		}
	}
	for _, e := range inline {
		add(e)
	}

	out := make([]models.FieldMapEntry, 0, len(order))
	for _, k := range order {
		out = append(out, byExt[k])
	}
	return out
}

// eventSource normalizes an event/dispatch payload into the Warmbly source field
// vocabulary the field map reads from.
func eventSource(data map[string]any) map[string]any {
	src := map[string]any{
		"email":      stringFromMap(data, "contact_email", "invitee_email", "email", "recipient"),
		"first_name": stringFromMap(data, "first_name", "contact_first_name"),
		"last_name":  stringFromMap(data, "last_name", "contact_last_name"),
		"name":       stringFromMap(data, "contact_name", "invitee_name", "name"),
		"company":    stringFromMap(data, "company", "contact_company"),
		"phone":      stringFromMap(data, "phone", "contact_phone"),
	}
	if cf, ok := data["custom_fields"]; ok {
		src["custom_fields"] = cf
	}
	return src
}

// contactEmail extracts the dedupe email from a dispatch payload.
func contactEmail(data map[string]any) string {
	return stringFromMap(data, "contact_email", "invitee_email", "email", "recipient")
}

// strProp reads a string value from a projected props map.
func strProp(props map[string]any, key string) string {
	if v, ok := props[key].(string); ok {
		return strings.TrimSpace(v)
	}
	return ""
}

// toStr coerces a projected value (always a string today) to string.
func toStr(v any) string {
	if s, ok := v.(string); ok {
		return s
	}
	return ""
}
