package recordsv1

import (
	"context"
	"encoding/json"
	"regexp"
	"sort"
	"strings"
	"time"
	"unicode/utf8"

	common "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
)

var (
	coefficientPattern = regexp.MustCompile(`^-?(0|[1-9][0-9]*)$`)
)

func SemanticRequestSize(request Request) (int, error) {
	data, err := json.Marshal(request)
	return len(data), err
}

func SemanticResponseSize(response Response) (int, error) {
	data, err := json.Marshal(response)
	return len(data), err
}

// ValidateRequest validates the transport-independent envelope. Metadata-bound
// existence, canonical case, and field-kind checks remain Runtime work.
func ValidateRequest(ctx context.Context, request Request) error {
	if err := contextFailure(ctx); err != nil {
		return err
	}
	if _, ok := ctx.Deadline(); !ok {
		return invalid(InvalidComponentDeadline, "")
	}
	if !common.ValidObjectIdentifier(request.ObjectID) {
		if len(request.ObjectID) > MaxObjectIdentifierBytes {
			return exhausted(LimitObjectID, MaxObjectIdentifierBytes)
		}
		return invalid(InvalidComponentObjectID, "")
	}
	if strings.TrimSpace(request.RecordID) == "" || !utf8.ValidString(request.RecordID) {
		return invalid(InvalidComponentRecordID, "")
	}
	if len(request.RecordID) > MaxRecordIdentifierBytes {
		return exhausted(LimitRecordID, MaxRecordIdentifierBytes)
	}
	if len(request.SelectedRootFields) == 0 {
		return invalid(InvalidComponentField, "")
	}
	if len(request.SelectedRootFields) > MaxSelectedFields {
		return exhausted(LimitSelectedFields, MaxSelectedFields)
	}
	canonical := make(map[string]struct{}, len(request.SelectedRootFields))
	for _, field := range request.SelectedRootFields {
		if !common.ValidFieldIdentifier(field) {
			if len(field) > MaxFieldIdentifierBytes {
				return exhausted(LimitFieldID, MaxFieldIdentifierBytes)
			}
			return invalid(InvalidComponentField, "")
		}
		canonical[strings.ToLower(field)] = struct{}{}
	}
	if len(canonical) > MaxSelectedFields {
		return exhausted(LimitSelectedFields, MaxSelectedFields)
	}
	size, err := SemanticRequestSize(request)
	if err != nil {
		return invalid(InvalidComponentResponse, "")
	}
	if size > MaxSemanticRequestBytes {
		return exhausted(LimitSemanticRequest, MaxSemanticRequestBytes)
	}
	return nil
}

func ValidateResponse(response Response) error {
	if strings.TrimSpace(response.RecordID) == "" || !utf8.ValidString(response.RecordID) {
		return &Failure{Category: FailureInternal}
	}
	if len(response.RecordID) > MaxRecordIdentifierBytes {
		return exhausted(LimitRecordID, MaxRecordIdentifierBytes)
	}
	if response.Receipts.ProtocolVersion != "1.0" || response.Receipts.CapabilityVersion != CapabilityName {
		return incompatible()
	}
	if len(response.Fields) > MaxSelectedFields {
		return exhausted(LimitSelectedFields, MaxSelectedFields)
	}
	if !sort.SliceIsSorted(response.Fields, func(i, j int) bool { return response.Fields[i].Name < response.Fields[j].Name }) {
		return &Failure{Category: FailureInternal}
	}
	previous := ""
	for i := range response.Fields {
		field := response.Fields[i]
		if !common.ValidFieldIdentifier(field.Name) || field.Name == previous {
			return &Failure{Category: FailureInternal}
		}
		previous = field.Name
		if err := validateField(field); err != nil {
			return err
		}
	}
	size, err := SemanticResponseSize(response)
	if err != nil {
		return &Failure{Category: FailureInternal}
	}
	if size > MaxSemanticResponseBytes {
		return exhausted(LimitSemanticResponse, MaxSemanticResponseBytes)
	}
	return nil
}

func validateField(field Field) error {
	if !field.Kind.Valid() {
		return &Failure{Category: FailureInternal}
	}
	arms := 0
	for _, present := range []bool{
		field.String != nil, field.Boolean != nil, field.Integer != nil,
		field.Decimal != nil, field.Date != nil, field.DateTime != nil, field.Reference != nil,
	} {
		if present {
			arms++
		}
	}
	if field.Null {
		if arms != 0 {
			return &Failure{Category: FailureInternal}
		}
		return nil
	}
	if arms != 1 {
		return &Failure{Category: FailureInternal}
	}
	valid := false
	switch field.Kind {
	case ValueKindString:
		valid = field.String != nil && utf8.ValidString(*field.String)
	case ValueKindBoolean:
		valid = field.Boolean != nil
	case ValueKindInteger:
		valid = field.Integer != nil
	case ValueKindDecimal:
		valid = field.Decimal != nil && coefficientPattern.MatchString(field.Decimal.Coefficient)
	case ValueKindDate:
		if field.Date != nil {
			parsed, err := time.Parse("2006-01-02", *field.Date)
			valid = err == nil && parsed.Format("2006-01-02") == *field.Date
		}
	case ValueKindDateTime:
		if field.DateTime != nil {
			parsed, err := time.Parse(time.RFC3339Nano, *field.DateTime)
			valid = err == nil && strings.HasSuffix(*field.DateTime, "Z") && parsed.Location() == time.UTC && parsed.Format(time.RFC3339Nano) == *field.DateTime
		}
	case ValueKindReference:
		valid = field.Reference != nil && utf8.ValidString(*field.Reference)
	}
	if !valid {
		return &Failure{Category: FailureInternal}
	}
	return nil
}

func contextFailure(ctx context.Context) error {
	if err := ctx.Err(); err != nil {
		if err == context.DeadlineExceeded {
			return &Failure{Category: FailureDeadlineExceeded}
		}
		return &Failure{Category: FailureCanceled}
	}
	return nil
}
