// Package mapping converts the stdlib-only private Catalog and Runtime
// semantic contracts to and from generated protobuf messages and safe gRPC
// status details.
package mapping

import (
	"context"
	"fmt"

	semantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordsv1"
	commonv1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/common/v1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
)

func ToWireRequest(ctx context.Context, request semantic.Request) (*runtimev1.ReadSelectedRootRequest, error) {
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.ReadSelectedRootRequest{
		ObjectId:           request.ObjectID,
		RecordId:           request.RecordID,
		SelectedRootFields: append([]string(nil), request.SelectedRootFields...),
		MinCatalogRevision: request.MinCatalogRevision,
	}, nil
}

func FromWireRequest(ctx context.Context, request *runtimev1.ReadSelectedRootRequest) (semantic.Request, error) {
	if request == nil {
		return semantic.Request{}, &semantic.Failure{Category: semantic.FailureInvalidRequest, InvalidComponent: semantic.InvalidComponentResponse}
	}
	result := semantic.Request{
		ObjectID:           request.GetObjectId(),
		RecordID:           request.GetRecordId(),
		SelectedRootFields: append([]string(nil), request.GetSelectedRootFields()...),
		MinCatalogRevision: request.MinCatalogRevision,
	}
	if err := semantic.ValidateRequest(ctx, result); err != nil {
		return semantic.Request{}, err
	}
	return result, nil
}

func ToWireResponse(response semantic.Response) (*runtimev1.ReadSelectedRootResponse, error) {
	if err := semantic.ValidateResponse(response); err != nil {
		return nil, err
	}
	fields := make([]*runtimev1.SelectedRootField, 0, len(response.Fields))
	for _, field := range response.Fields {
		wireField, err := toWireField(field)
		if err != nil {
			return nil, err
		}
		fields = append(fields, wireField)
	}
	return &runtimev1.ReadSelectedRootResponse{
		RecordId: response.RecordID,
		Fields:   fields,
		Receipts: &commonv1.ConsistencyReceipts{
			ProtocolVersion:   &commonv1.ProtocolVersion{Major: semantic.ProtocolMajor, Minor: semantic.ProtocolMinor},
			CatalogRevision:   response.Receipts.CatalogRevision,
			SchemaGeneration:  response.Receipts.SchemaGeneration,
			EngineGeneration:  response.Receipts.EngineGeneration,
			SecurityEpoch:     response.Receipts.SecurityEpoch,
			CapabilityVersion: &commonv1.CapabilityVersion{Name: semantic.CapabilityName, Major: 1, Minor: 0},
		},
	}, nil
}

func FromWireResponse(response *runtimev1.ReadSelectedRootResponse) (semantic.Response, error) {
	if response == nil || response.GetReceipts() == nil || response.GetReceipts().GetProtocolVersion() == nil || response.GetReceipts().GetCapabilityVersion() == nil {
		return semantic.Response{}, incompatibleFailure()
	}
	protocol := response.GetReceipts().GetProtocolVersion()
	capability := response.GetReceipts().GetCapabilityVersion()
	if protocol.GetMajor() != semantic.ProtocolMajor || protocol.GetMinor() != semantic.ProtocolMinor ||
		capability.GetName() != semantic.CapabilityName || capability.GetMajor() != 1 || capability.GetMinor() != 0 {
		return semantic.Response{}, incompatibleFailure()
	}
	result := semantic.Response{
		RecordID: response.GetRecordId(),
		Receipts: semantic.Receipts{
			ProtocolVersion:   "1.0",
			CatalogRevision:   response.GetReceipts().GetCatalogRevision(),
			SchemaGeneration:  response.GetReceipts().GetSchemaGeneration(),
			EngineGeneration:  response.GetReceipts().GetEngineGeneration(),
			SecurityEpoch:     response.GetReceipts().GetSecurityEpoch(),
			CapabilityVersion: semantic.CapabilityName,
		},
	}
	result.Fields = make([]semantic.Field, 0, len(response.GetFields()))
	for _, field := range response.GetFields() {
		semanticField, err := fromWireField(field)
		if err != nil {
			return semantic.Response{}, err
		}
		result.Fields = append(result.Fields, semanticField)
	}
	if err := semantic.ValidateResponse(result); err != nil {
		return semantic.Response{}, err
	}
	return result, nil
}

func toWireField(field semantic.Field) (*runtimev1.SelectedRootField, error) {
	kind, ok := toWireKind(field.Kind)
	if !ok {
		return nil, fmt.Errorf("unsupported selected-root semantic value kind")
	}
	result := &runtimev1.SelectedRootField{Name: field.Name, DeclaredKind: kind}
	if field.Null {
		result.Value = &runtimev1.SelectedRootField_NullValue{NullValue: &runtimev1.NullValue{}}
		return result, nil
	}
	switch field.Kind {
	case semantic.ValueKindString:
		result.Value = &runtimev1.SelectedRootField_StringValue{StringValue: *field.String}
	case semantic.ValueKindBoolean:
		result.Value = &runtimev1.SelectedRootField_BooleanValue{BooleanValue: *field.Boolean}
	case semantic.ValueKindInteger:
		result.Value = &runtimev1.SelectedRootField_IntegerValue{IntegerValue: *field.Integer}
	case semantic.ValueKindDecimal:
		result.Value = &runtimev1.SelectedRootField_DecimalValue{DecimalValue: &runtimev1.DecimalValue{Coefficient: field.Decimal.Coefficient, Scale: field.Decimal.Scale}}
	case semantic.ValueKindDate:
		result.Value = &runtimev1.SelectedRootField_DateValue{DateValue: &runtimev1.DateValue{CalendarDate: *field.Date}}
	case semantic.ValueKindDateTime:
		result.Value = &runtimev1.SelectedRootField_DatetimeValue{DatetimeValue: &runtimev1.DateTimeValue{UtcRfc3339: *field.DateTime}}
	case semantic.ValueKindReference:
		result.Value = &runtimev1.SelectedRootField_ReferenceValue{ReferenceValue: &runtimev1.ReferenceValue{Identifier: *field.Reference}}
	default:
		return nil, fmt.Errorf("unsupported selected-root semantic value kind")
	}
	return result, nil
}

func fromWireField(field *runtimev1.SelectedRootField) (semantic.Field, error) {
	if field == nil {
		return semantic.Field{}, &semantic.Failure{Category: semantic.FailureInternal}
	}
	result := semantic.Field{Name: field.GetName(), Kind: fromWireKind(field.GetDeclaredKind())}
	if !result.Kind.Valid() {
		return semantic.Field{}, &semantic.Failure{Category: semantic.FailureInternal}
	}
	switch value := field.GetValue().(type) {
	case *runtimev1.SelectedRootField_StringValue:
		result.String = &value.StringValue
	case *runtimev1.SelectedRootField_BooleanValue:
		result.Boolean = &value.BooleanValue
	case *runtimev1.SelectedRootField_IntegerValue:
		result.Integer = &value.IntegerValue
	case *runtimev1.SelectedRootField_DecimalValue:
		if value.DecimalValue == nil {
			return semantic.Field{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		result.Decimal = &semantic.Decimal{Coefficient: value.DecimalValue.GetCoefficient(), Scale: value.DecimalValue.GetScale()}
	case *runtimev1.SelectedRootField_DateValue:
		if value.DateValue == nil {
			return semantic.Field{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		v := value.DateValue.GetCalendarDate()
		result.Date = &v
	case *runtimev1.SelectedRootField_DatetimeValue:
		if value.DatetimeValue == nil {
			return semantic.Field{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		v := value.DatetimeValue.GetUtcRfc3339()
		result.DateTime = &v
	case *runtimev1.SelectedRootField_ReferenceValue:
		if value.ReferenceValue == nil {
			return semantic.Field{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		v := value.ReferenceValue.GetIdentifier()
		result.Reference = &v
	case *runtimev1.SelectedRootField_NullValue:
		if value.NullValue == nil {
			return semantic.Field{}, &semantic.Failure{Category: semantic.FailureInternal}
		}
		result.Null = true
	default:
		return semantic.Field{}, &semantic.Failure{Category: semantic.FailureInternal}
	}
	return result, nil
}

func toWireKind(kind semantic.ValueKind) (runtimev1.ValueKind, bool) {
	result, ok := map[semantic.ValueKind]runtimev1.ValueKind{
		semantic.ValueKindString:    runtimev1.ValueKind_VALUE_KIND_STRING,
		semantic.ValueKindBoolean:   runtimev1.ValueKind_VALUE_KIND_BOOLEAN,
		semantic.ValueKindInteger:   runtimev1.ValueKind_VALUE_KIND_INTEGER,
		semantic.ValueKindDecimal:   runtimev1.ValueKind_VALUE_KIND_DECIMAL,
		semantic.ValueKindDate:      runtimev1.ValueKind_VALUE_KIND_DATE,
		semantic.ValueKindDateTime:  runtimev1.ValueKind_VALUE_KIND_DATETIME,
		semantic.ValueKindReference: runtimev1.ValueKind_VALUE_KIND_REFERENCE,
	}[kind]
	return result, ok
}

func fromWireKind(kind runtimev1.ValueKind) semantic.ValueKind {
	return map[runtimev1.ValueKind]semantic.ValueKind{
		runtimev1.ValueKind_VALUE_KIND_STRING:    semantic.ValueKindString,
		runtimev1.ValueKind_VALUE_KIND_BOOLEAN:   semantic.ValueKindBoolean,
		runtimev1.ValueKind_VALUE_KIND_INTEGER:   semantic.ValueKindInteger,
		runtimev1.ValueKind_VALUE_KIND_DECIMAL:   semantic.ValueKindDecimal,
		runtimev1.ValueKind_VALUE_KIND_DATE:      semantic.ValueKindDate,
		runtimev1.ValueKind_VALUE_KIND_DATETIME:  semantic.ValueKindDateTime,
		runtimev1.ValueKind_VALUE_KIND_REFERENCE: semantic.ValueKindReference,
	}[kind]
}

func incompatibleFailure() *semantic.Failure {
	return incompatibleFailureFor(selectedRootCapability)
}

func incompatibleFailureFor(capability capabilitySpec) *semantic.Failure {
	return &semantic.Failure{
		Category:           semantic.FailureIncompatible,
		RequiredCapability: capability.name,
		SupportedMajor:     capability.protocolMajor,
		SupportedMinorMin:  capability.protocolMinor,
		SupportedMinorMax:  capability.protocolMinor,
	}
}
