//go:build !lite

package service

import (
	"fmt"
	"sort"
	"strings"
)

// validFields is the set of JSON field names that can be selected on TransactionResponse.
var validFields = map[string]bool{
	"id":                           true,
	"short_id":                     true,
	"account_id":                   true,
	"account_name":                 true,
	"user_name":                    true,
	"amount":                       true,
	"iso_currency_code":            true,
	"date":                         true,
	"authorized_date":              true,
	"datetime":                     true,
	"authorized_datetime":          true,
	"provider_name":                true,
	"provider_merchant_name":       true,
	"category":                     true,
	"category_override":            true,
	"provider_category_primary":    true,
	"provider_category_detailed":   true,
	"provider_category_confidence": true,
	"provider_payment_channel":     true,
	"pending":                      true,
	"created_at":                   true,
	"updated_at":                   true,
	"metadata":                     true,
	"flagged_at":                   true,
}

// fieldAliases expand shorthand names to groups of fields.
var fieldAliases = map[string][]string{
	"minimal":    {"provider_name", "amount", "date"},
	"core":       {"id", "date", "amount", "provider_name", "iso_currency_code"},
	"category":   {"category", "provider_category_primary", "provider_category_detailed"},
	"timestamps": {"created_at", "updated_at", "datetime", "authorized_datetime"},
}

// ParseFields parses and validates the transaction fields query parameter.
// Returns nil if no field selection (return all fields).
func ParseFields(raw string) (map[string]bool, error) {
	return parseFieldsWith(raw, validFields, fieldAliases)
}

// parseFieldsWith is the generic field-selection parser shared by every
// entity's Parse*Fields wrapper. valid is the set of selectable JSON field
// names; aliases expand shorthand names to groups. It returns nil for an empty
// selection (caller should return the full struct). id and short_id are always
// included so a row is always identifiable.
func parseFieldsWith(raw string, valid map[string]bool, aliases map[string][]string) (map[string]bool, error) {
	if raw == "" {
		return nil, nil
	}
	fields := make(map[string]bool)
	var unknown []string
	for _, f := range strings.Split(raw, ",") {
		f = strings.TrimSpace(f)
		if f == "" {
			continue
		}
		if expanded, ok := aliases[f]; ok {
			for _, ef := range expanded {
				fields[ef] = true
			}
			continue
		}
		if !valid[f] {
			unknown = append(unknown, f)
			continue
		}
		fields[f] = true
	}
	if len(unknown) > 0 {
		validList := make([]string, 0, len(valid)+len(aliases))
		for k := range valid {
			validList = append(validList, k)
		}
		for k := range aliases {
			validList = append(validList, k)
		}
		sort.Strings(validList)
		return nil, fmt.Errorf("unknown field(s): %s. Valid fields: %s", strings.Join(unknown, ", "), strings.Join(validList, ", "))
	}
	fields["id"] = true       // always include
	fields["short_id"] = true // always include
	return fields, nil
}

// NormalizeTransactionAttribution merges attributed_user into user_name and
// clears the attributed_user_* fields. This gives MCP consumers a single
// "user_name" that always reflects the effective user (attributed or owner).
func NormalizeTransactionAttribution(t *TransactionResponse) {
	if t.AttributedUserName != nil {
		t.UserName = t.AttributedUserName
	}
	t.AttributedUserID = nil
	t.AttributedUserName = nil
}

// FilterTransactionFields returns a map with only the requested fields.
// If fields is nil, returns nil to signal the caller should use the full struct.
//
// This iterates the requested field set once (O(requested)) rather than
// performing a fixed O(22) map lookup per call. For small field sets
// (e.g. "core" = 5 fields) this is several times faster per transaction,
// and the total work is proportional to what the caller asked for.
func FilterTransactionFields(t TransactionResponse, fields map[string]bool) map[string]any {
	if fields == nil {
		return nil
	}
	m := make(map[string]any, len(fields))
	for key, want := range fields {
		if !want {
			continue
		}
		switch key {
		case "id":
			m["id"] = t.ID
		case "short_id":
			m["short_id"] = t.ShortID
		case "account_id":
			m["account_id"] = t.AccountID
		case "account_name":
			m["account_name"] = t.AccountName
		case "user_name":
			// Use attributed user when set (account linking), otherwise connection owner.
			if t.AttributedUserName != nil {
				m["user_name"] = t.AttributedUserName
			} else {
				m["user_name"] = t.UserName
			}
		case "amount":
			m["amount"] = t.Amount
		case "iso_currency_code":
			m["iso_currency_code"] = t.IsoCurrencyCode
		case "date":
			m["date"] = t.Date
		case "authorized_date":
			m["authorized_date"] = t.AuthorizedDate
		case "datetime":
			m["datetime"] = t.Datetime
		case "authorized_datetime":
			m["authorized_datetime"] = t.AuthorizedDatetime
		case "provider_name":
			m["provider_name"] = t.ProviderName
		case "provider_merchant_name":
			m["provider_merchant_name"] = t.ProviderMerchantName
		case "category":
			m["category"] = t.Category
		case "category_override":
			m["category_override"] = t.CategoryOverride
		case "provider_category_primary":
			m["provider_category_primary"] = t.ProviderCategoryPrimary
		case "provider_category_detailed":
			m["provider_category_detailed"] = t.ProviderCategoryDetailed
		case "provider_category_confidence":
			m["provider_category_confidence"] = t.ProviderCategoryConfidence
		case "provider_payment_channel":
			m["provider_payment_channel"] = t.ProviderPaymentChannel
		case "pending":
			m["pending"] = t.Pending
		case "created_at":
			m["created_at"] = t.CreatedAt
		case "updated_at":
			m["updated_at"] = t.UpdatedAt
		case "metadata":
			m["metadata"] = t.Metadata
		case "flagged_at":
			m["flagged_at"] = t.FlaggedAt
		}
		// Keys outside the switch are silently ignored. ParseFields already
		// rejects unknown names; review delegation strips "transaction." before
		// calling this function, so callers always pass valid field names.
	}
	return m
}
