package packs

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

	connector "github.com/pingcap-inc/nexuscrm-contract/pkg/connector"
)

// LoadPersistedConnectorFieldMappingDirections returns both Runtime write and
// durable-finalization projections from one exact component. Either side may
// be empty; the caller decides which closure its selected contracts require.
func LoadPersistedConnectorFieldMappingDirections(
	raw string,
	owner string,
	businessKey string,
) (inbound []connector.ConnectorFieldMappingInput, outbound []connector.ConnectorFieldMappingInput, err error) {
	owner = strings.TrimSpace(owner)
	businessKey = strings.TrimSpace(businessKey)
	if owner == "" || businessKey == "" {
		return nil, nil, fmt.Errorf("connector field mapping owner and business key are required")
	}
	selected, err := persistedConnectorFieldMappingRow(raw, businessKey)
	if err != nil {
		return nil, nil, err
	}
	values, err := decodePersistedConnectorFieldMappings(selected, owner)
	if err != nil {
		return nil, nil, err
	}
	hasInbound := false
	for _, value := range values {
		if value.ProjectionOnly {
			continue
		}
		if value.Direction == connector.DirectionInbound ||
			value.Direction == connector.DirectionBidirectional {
			hasInbound = true
		}
		if value.Direction == connector.DirectionOutbound ||
			value.Direction == connector.DirectionBidirectional {
			outbound = append(outbound, value)
		}
	}
	if hasInbound {
		inbound, err = decodePersistedInboundConnectorFieldMappings(selected, owner)
		if err != nil {
			return nil, nil, err
		}
	}
	sort.Slice(outbound, func(i, j int) bool {
		leftRank := 1
		if outbound[i].Direction == connector.DirectionOutbound {
			leftRank = 0
		}
		rightRank := 1
		if outbound[j].Direction == connector.DirectionOutbound {
			rightRank = 0
		}
		if leftRank != rightRank {
			return leftRank < rightRank
		}
		if outbound[i].SortOrder != outbound[j].SortOrder {
			return outbound[i].SortOrder < outbound[j].SortOrder
		}
		if !strings.EqualFold(outbound[i].SourceField, outbound[j].SourceField) {
			return strings.ToLower(outbound[i].SourceField) < strings.ToLower(outbound[j].SourceField)
		}
		return strings.ToLower(outbound[i].TargetField) < strings.ToLower(outbound[j].TargetField)
	})
	return inbound, outbound, nil
}

func persistedConnectorFieldMappingRow(
	raw string,
	businessKey string,
) (runtimeStartupShallowDeclaredRow, error) {
	rows, err := persistedRuntimeDeclarationRows(raw)
	if err != nil {
		return runtimeStartupShallowDeclaredRow{}, err
	}
	var selected *runtimeStartupShallowDeclaredRow
	for index := range rows {
		row := rows[index]
		if row.Kind != ComponentKindConnectorFieldMapping || row.BusinessKey != businessKey {
			continue
		}
		if selected != nil {
			return runtimeStartupShallowDeclaredRow{}, fmt.Errorf(
				"connector field mapping business key %q is ambiguous",
				businessKey,
			)
		}
		copy := row
		selected = &copy
	}
	if selected == nil {
		return runtimeStartupShallowDeclaredRow{}, fmt.Errorf(
			"connector field mapping business key %q is absent from persisted generation",
			businessKey,
		)
	}
	return *selected, nil
}

func normalizePersistedConnectorFieldMappingProjection(
	row runtimeStartupShallowDeclaredRow,
) (string, error) {
	owner, err := persistedConnectorFieldMappingProjectionOwner(row)
	if err != nil {
		return "", err
	}
	return normalizePersistedConnectorFieldMappingProjectionForOwner(row, owner)
}

func normalizePersistedConnectorFieldMappingProjectionForOwner(
	row runtimeStartupShallowDeclaredRow,
	owner string,
) (string, error) {
	owner = strings.TrimSpace(owner)
	if owner == "" {
		return "", fmt.Errorf("connector field mapping owner is required")
	}
	businessKeyOwner := strings.TrimSpace(strings.SplitN(row.BusinessKey, "/", 2)[0])
	if businessKeyOwner == "" || !strings.EqualFold(businessKeyOwner, owner) {
		return "", fmt.Errorf(
			"connector field mapping business key owner %q disagrees with %q",
			businessKeyOwner,
			owner,
		)
	}
	values, err := decodePersistedConnectorFieldMappings(row, owner)
	if err != nil {
		return "", err
	}
	return marshalRuntimeStartupShallowProjection(values)
}

func persistedConnectorFieldMappingProjectionOwner(
	row runtimeStartupShallowDeclaredRow,
) (string, error) {
	if strings.TrimSpace(row.Projection) == "" {
		return "", fmt.Errorf("projection is required")
	}
	var values []connector.ConnectorFieldMappingInput
	if err := decodeStrictJSON([]byte(row.Projection), &values); err != nil {
		return "", err
	}
	if len(values) == 0 {
		return "", fmt.Errorf("projection must contain at least one mapping")
	}
	owner := values[0].Source
	if owner == "" || strings.TrimSpace(owner) != owner {
		return "", fmt.Errorf("connector field mapping projection owner %q is invalid", owner)
	}
	return owner, nil
}

func decodePersistedInboundConnectorFieldMappings(
	row runtimeStartupShallowDeclaredRow,
	owner string,
) ([]connector.ConnectorFieldMappingInput, error) {
	values, err := decodePersistedConnectorFieldMappings(row, owner)
	if err != nil {
		return nil, err
	}
	type selectedMapping struct {
		value connector.ConnectorFieldMappingInput
		exact bool
	}
	seen := make(map[string]selectedMapping)
	for _, value := range values {
		if value.ProjectionOnly ||
			(value.Direction != connector.DirectionInbound &&
				value.Direction != connector.DirectionBidirectional) {
			continue
		}
		exact := value.Direction == connector.DirectionInbound
		value.Direction = connector.DirectionInbound
		key := persistedConnectorFieldMappingKey(value)
		if previous, exists := seen[key]; exists {
			switch {
			case previous.exact && !exact:
				continue
			case exact && !previous.exact:
				// Exact inbound replaces its bidirectional fallback.
			case previous.value != value:
				return nil, fmt.Errorf("connector field mapping %q has ambiguous inbound precedence", key)
			default:
				continue
			}
		}
		seen[key] = selectedMapping{value: value, exact: exact}
	}
	out := make([]connector.ConnectorFieldMappingInput, 0, len(seen))
	for _, selected := range seen {
		out = append(out, selected.value)
	}
	if len(out) == 0 {
		return nil, fmt.Errorf("persisted connector field mapping has no active inbound rows")
	}
	sort.Slice(out, func(i, j int) bool {
		return persistedConnectorFieldMappingKey(out[i]) <
			persistedConnectorFieldMappingKey(out[j])
	})
	if err := connector.ValidateConnectorFieldMappingInputSet(out); err != nil {
		return nil, fmt.Errorf("validate persisted inbound connector field mappings: %w", err)
	}
	return out, nil
}

func decodePersistedConnectorFieldMappings(
	row runtimeStartupShallowDeclaredRow,
	owner string,
) ([]connector.ConnectorFieldMappingInput, error) {
	if strings.TrimSpace(row.Projection) == "" {
		return nil, fmt.Errorf("projection is required")
	}
	var values []connector.ConnectorFieldMappingInput
	if err := decodeStrictJSON([]byte(row.Projection), &values); err != nil {
		return nil, err
	}
	if len(values) == 0 {
		return nil, fmt.Errorf("projection must contain at least one mapping")
	}
	for index := range values {
		value := &values[index]
		if value.Source != owner || strings.TrimSpace(value.Source) != value.Source {
			return nil, fmt.Errorf("mapping row %d owner %q disagrees with %q", index, value.Source, owner)
		}
	}
	if err := connector.ValidateConnectorFieldMappingInputSet(values); err != nil {
		return nil, err
	}
	return values, nil
}

func persistedRuntimeDeclarationRows(raw string) ([]runtimeStartupShallowDeclaredRow, error) {
	trimmed := strings.TrimSpace(raw)
	if trimmed == "" || trimmed == "null" {
		return nil, fmt.Errorf("persisted Runtime declaration is missing")
	}
	var messages []json.RawMessage
	if err := json.Unmarshal([]byte(trimmed), &messages); err != nil {
		return nil, fmt.Errorf("parse persisted Runtime declaration: %w", err)
	}
	rows := make([]runtimeStartupShallowDeclaredRow, 0, len(messages))
	for index, message := range messages {
		if strings.TrimSpace(string(message)) == "null" {
			return nil, fmt.Errorf("persisted Runtime declaration row %d must be an object", index)
		}
		var row runtimeStartupShallowDeclaredRow
		if err := decodeStrictJSON(message, &row); err != nil {
			return nil, fmt.Errorf("parse persisted Runtime declaration row %d: %w", index, err)
		}
		row.Kind = ComponentKind(strings.TrimSpace(string(row.Kind)))
		row.BusinessKey = strings.TrimSpace(row.BusinessKey)
		row.Projection = strings.TrimSpace(row.Projection)
		if row.Kind == "" || row.BusinessKey == "" {
			return nil, fmt.Errorf("persisted Runtime declaration row %d is missing kind or business key", index)
		}
		rows = append(rows, row)
	}
	return rows, nil
}

func persistedConnectorFieldMappingKey(value connector.ConnectorFieldMappingInput) string {
	return strings.Join([]string{
		value.Source,
		value.SourceObject,
		value.TargetObject,
		value.ActionType,
		value.SourceField,
		value.Direction,
	}, "\x00")
}
