package metadata

import (
	"bytes"
	"encoding/json"
	"fmt"

	apperrors "github.com/nexuscrm/kernel/pkg/errors"
	"github.com/nexuscrm/shared/pkg/models"
)

func mergeFlowMetadataRaw(existingRaw json.RawMessage, patchRaw json.RawMessage) (json.RawMessage, error) {
	var existing map[string]json.RawMessage
	if len(bytes.TrimSpace(existingRaw)) == 0 {
		existing = map[string]json.RawMessage{}
	} else if err := json.Unmarshal(existingRaw, &existing); err != nil {
		return nil, apperrors.NewInvalidFieldError("Existing Flow metadata is malformed").WithCause(err)
	}

	var patch map[string]json.RawMessage
	if err := json.Unmarshal(patchRaw, &patch); err != nil {
		return nil, newInvalidFlowMetadataPatchError("Metadata", "Metadata must be a JSON object")
	}
	if patch == nil {
		return nil, newInvalidFlowMetadataPatchError("Metadata", "Metadata must be a JSON object")
	}
	if err := rejectNullFlowMetadataRaw("Metadata", patchRaw); err != nil {
		return nil, err
	}

	merged, err := deepMergeRawJSONObjects(existing, patch)
	if err != nil {
		return nil, newInvalidFlowMetadataPatchError("Metadata", "Metadata must be a JSON object")
	}
	data, err := json.Marshal(merged)
	if err != nil {
		return nil, apperrors.NewInvalidFieldError("Failed to serialize merged Flow metadata").WithCause(err)
	}
	return data, nil
}

func rejectNullFlowMetadataRaw(path string, raw json.RawMessage) error {
	trimmed := bytes.TrimSpace(raw)
	if bytes.Equal(trimmed, []byte("null")) {
		return newInvalidFlowMetadataPatchError(path, path+" cannot be null")
	}
	if len(trimmed) == 0 {
		return nil
	}
	switch trimmed[0] {
	case '{':
		var fields map[string]json.RawMessage
		if err := json.Unmarshal(trimmed, &fields); err != nil {
			return nil
		}
		for key, nested := range fields {
			if err := rejectNullFlowMetadataRaw(path+"."+key, nested); err != nil {
				return err
			}
		}
	case '[':
		var items []json.RawMessage
		if err := json.Unmarshal(trimmed, &items); err != nil {
			return nil
		}
		for idx, nested := range items {
			if err := rejectNullFlowMetadataRaw(fmt.Sprintf("%s[%d]", path, idx), nested); err != nil {
				return err
			}
		}
	}
	return nil
}

func deepMergeRawJSONObjects(dst map[string]json.RawMessage, patch map[string]json.RawMessage) (map[string]json.RawMessage, error) {
	if dst == nil {
		dst = map[string]json.RawMessage{}
	}
	for key, patchValue := range patch {
		patchMap, patchIsObject, err := decodeJSONObjectRaw(patchValue)
		if err != nil {
			return nil, err
		}
		if patchIsObject {
			existingMap, existingIsObject, err := decodeJSONObjectRaw(dst[key])
			if err != nil {
				return nil, err
			}
			if existingIsObject {
				merged, err := deepMergeRawJSONObjects(existingMap, patchMap)
				if err != nil {
					return nil, err
				}
				data, err := json.Marshal(merged)
				if err != nil {
					return nil, err
				}
				dst[key] = data
				continue
			}
		}
		dst[key] = append(json.RawMessage(nil), patchValue...)
	}
	return dst, nil
}

func decodeJSONObjectRaw(raw json.RawMessage) (map[string]json.RawMessage, bool, error) {
	trimmed := bytes.TrimSpace(raw)
	if len(trimmed) == 0 || trimmed[0] != '{' {
		return nil, false, nil
	}
	var fields map[string]json.RawMessage
	if err := json.Unmarshal(trimmed, &fields); err != nil {
		return nil, false, err
	}
	if fields == nil {
		return nil, false, nil
	}
	return fields, true, nil
}

func cloneFlowVersion(existing *models.FlowVersion) *models.FlowVersion {
	if existing == nil {
		return nil
	}
	cloned := *existing
	if len(existing.MetadataRaw) > 0 {
		cloned.MetadataRaw = append(json.RawMessage(nil), existing.MetadataRaw...)
	}
	return &cloned
}

func newInvalidFlowMetadataPatchError(field string, message string) error {
	return apperrors.NewInvalidFieldError(message, field)
}
