package contacts

import (
	"encoding/json"
	"sort"
	"strings"

	"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
	"github.com/ultisuite/ulti-backend/internal/nextcloud"
)

const (
	maxImportBody       = 8 << 20 // 8 MiB
	maxContactsPerImport = 5000
)

// importRequest is the bulk import body. Each element of contacts is either a
// raw vCard string ("BEGIN:VCARD...") or a structured contact object matching
// the single-create shape (full_name/email/phone/org or raw_vcard).
type importRequest struct {
	Contacts []json.RawMessage `json:"contacts"`
}

// parsedImport holds the valid contacts to send and a mapping back to the
// caller's original array index (so failures can be reported precisely).
type parsedImport struct {
	contacts      []nextcloud.Contact
	originalIndex []int
}

func parseImportContacts(items []json.RawMessage) (parsedImport, []ImportFailure, *apivalidate.ValidationError) {
	if len(items) == 0 {
		return parsedImport{}, nil, apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "contacts", Message: "at least one contact is required",
		})
	}
	if len(items) > maxContactsPerImport {
		return parsedImport{}, nil, apivalidate.NewValidationError(apivalidate.FieldDetail{
			Field: "contacts", Message: "too many contacts in a single import",
		})
	}

	out := parsedImport{
		contacts:      make([]nextcloud.Contact, 0, len(items)),
		originalIndex: make([]int, 0, len(items)),
	}
	preFailures := make([]ImportFailure, 0)

	for i, raw := range items {
		contact, err := decodeImportItem(raw)
		if err != "" {
			preFailures = append(preFailures, ImportFailure{Index: i, Error: err})
			continue
		}
		out.contacts = append(out.contacts, contact)
		out.originalIndex = append(out.originalIndex, i)
	}

	return out, preFailures, nil
}

// decodeImportItem turns a single array element into a Contact, returning a
// non-empty error string when the element is unusable.
func decodeImportItem(raw json.RawMessage) (nextcloud.Contact, string) {
	trimmed := strings.TrimSpace(string(raw))
	if trimmed == "" || trimmed == "null" {
		return nextcloud.Contact{}, "empty contact"
	}

	// A JSON string element is treated as a raw vCard.
	if trimmed[0] == '"' {
		var vcard string
		if err := json.Unmarshal(raw, &vcard); err != nil {
			return nextcloud.Contact{}, "invalid vcard string"
		}
		vcard = strings.TrimSpace(vcard)
		if vcard == "" {
			return nextcloud.Contact{}, "empty vcard"
		}
		return nextcloud.Contact{RawVCard: vcard}, ""
	}

	var contact nextcloud.Contact
	if err := json.Unmarshal(raw, &contact); err != nil {
		return nextcloud.Contact{}, "invalid contact object"
	}
	if strings.TrimSpace(contact.RawVCard) == "" && strings.TrimSpace(contact.FullName) == "" {
		return nextcloud.Contact{}, "full_name or raw_vcard required"
	}
	return contact, ""
}

// mergeImportFailures combines pre-send validation failures with service send
// failures, remapping service indices (relative to the valid slice) back to the
// caller's original array indices, sorted ascending.
func mergeImportFailures(pre []ImportFailure, svc []ImportFailure, originalIndex []int) []ImportFailure {
	merged := make([]ImportFailure, 0, len(pre)+len(svc))
	merged = append(merged, pre...)
	for _, f := range svc {
		idx := f.Index
		if idx >= 0 && idx < len(originalIndex) {
			idx = originalIndex[idx]
		}
		merged = append(merged, ImportFailure{Index: idx, Error: f.Error})
	}
	sort.SliceStable(merged, func(i, j int) bool {
		return merged[i].Index < merged[j].Index
	})
	return merged
}
