package mapping

import (
	"context"

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

// ToWireResolveAttachmentSourcesRequest maps a validated source proof request
// without adding product source grammar or caller-selected authority.
func ToWireResolveAttachmentSourcesRequest(ctx context.Context, request semantic.ResolveRequest) (*runtimev1.ResolveAttachmentSourcesRequest, error) {
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return nil, err
	}
	sources := make([]*runtimev1.AttachmentSourceProof, 0, len(request.Sources))
	for _, source := range request.Sources {
		sources = append(sources, &runtimev1.AttachmentSourceProof{
			SourceFieldId: source.SourceFieldID, SourceValue: source.SourceValue, SourceValueOffset: source.SourceValueOffset,
		})
	}
	return &runtimev1.ResolveAttachmentSourcesRequest{
		ParentObjectId: request.ParentObjectID, ParentRecordId: request.ParentRecordID,
		ResolverId: request.ResolverID, Sources: sources, MinCatalogRevision: request.MinCatalogRevision,
	}, nil
}

// FromWireResolveAttachmentSourcesRequest rejects nil proof elements and all
// malformed values before a Runtime adapter can inspect Catalog or records.
func FromWireResolveAttachmentSourcesRequest(ctx context.Context, request *runtimev1.ResolveAttachmentSourcesRequest) (semantic.ResolveRequest, error) {
	if request == nil {
		return semantic.ResolveRequest{}, recordAttachmentSourceInvalid(semantic.InvalidComponentResponse)
	}
	if len(request.GetSources()) > semantic.MaxSources {
		return semantic.ResolveRequest{}, recordAttachmentSourceExhausted(semantic.LimitOperationCount, semantic.MaxSources)
	}
	result := semantic.ResolveRequest{
		ParentObjectID: request.GetParentObjectId(), ParentRecordID: request.GetParentRecordId(),
		ResolverID: request.GetResolverId(), MinCatalogRevision: request.MinCatalogRevision,
		Sources: make([]semantic.Source, 0, len(request.GetSources())),
	}
	for _, source := range request.GetSources() {
		if source == nil {
			return semantic.ResolveRequest{}, recordAttachmentSourceInvalid(semantic.InvalidComponentResponse)
		}
		result.Sources = append(result.Sources, semantic.Source{
			SourceFieldID: source.GetSourceFieldId(), SourceValue: source.GetSourceValue(), SourceValueOffset: source.GetSourceValueOffset(),
		})
	}
	if err := semantic.ValidateRequest(ctx, result); err != nil {
		return semantic.ResolveRequest{}, err
	}
	return result, nil
}

func ToWireResolveAttachmentSourcesResponse(request semantic.ResolveRequest, response semantic.ResolveResponse) (*runtimev1.ResolveAttachmentSourcesResponse, error) {
	if err := semantic.ValidateResponse(request, response); err != nil {
		return nil, err
	}
	results := make([]*runtimev1.AttachmentSourceResolutionResult, 0, len(response.Results))
	for _, result := range response.Results {
		results = append(results, &runtimev1.AttachmentSourceResolutionResult{
			Index: result.Index, Outcome: toWireAttachmentSourceResolutionOutcome(result.Outcome), FileId: result.FileID, FileName: result.FileName,
		})
	}
	return &runtimev1.ResolveAttachmentSourcesResponse{
		Results: results, Receipts: toWireReceipts(response.Receipts, recordAttachmentSourceCapability),
	}, nil
}

func FromWireResolveAttachmentSourcesResponse(request semantic.ResolveRequest, response *runtimev1.ResolveAttachmentSourcesResponse) (semantic.ResolveResponse, error) {
	if response == nil || len(response.GetResults()) != len(request.Sources) || len(response.GetResults()) > semantic.MaxSources {
		return semantic.ResolveResponse{}, recordAttachmentSourceInternal()
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), recordAttachmentSourceCapability)
	if err != nil {
		return semantic.ResolveResponse{}, err
	}
	result := semantic.ResolveResponse{Receipts: receipts, Results: make([]semantic.SourceResult, 0, len(response.GetResults()))}
	for _, wireResult := range response.GetResults() {
		if wireResult == nil {
			return semantic.ResolveResponse{}, recordAttachmentSourceInternal()
		}
		outcome, ok := fromWireAttachmentSourceResolutionOutcome(wireResult.GetOutcome())
		if !ok {
			return semantic.ResolveResponse{}, recordAttachmentSourceInternal()
		}
		result.Results = append(result.Results, semantic.SourceResult{
			Index: wireResult.GetIndex(), Outcome: outcome, FileID: wireResult.GetFileId(), FileName: wireResult.GetFileName(),
		})
	}
	if err := semantic.ValidateResponse(request, result); err != nil {
		return semantic.ResolveResponse{}, err
	}
	return result, nil
}

func toWireAttachmentSourceResolutionOutcome(outcome semantic.SourceOutcome) runtimev1.AttachmentSourceResolutionOutcome {
	switch outcome {
	case semantic.SourceNotFound:
		return runtimev1.AttachmentSourceResolutionOutcome_ATTACHMENT_SOURCE_RESOLUTION_OUTCOME_NOT_FOUND
	case semantic.SourceUnavailable:
		return runtimev1.AttachmentSourceResolutionOutcome_ATTACHMENT_SOURCE_RESOLUTION_OUTCOME_UNAVAILABLE
	case semantic.SourceResolved:
		return runtimev1.AttachmentSourceResolutionOutcome_ATTACHMENT_SOURCE_RESOLUTION_OUTCOME_RESOLVED
	default:
		return runtimev1.AttachmentSourceResolutionOutcome_ATTACHMENT_SOURCE_RESOLUTION_OUTCOME_UNSPECIFIED
	}
}

func fromWireAttachmentSourceResolutionOutcome(outcome runtimev1.AttachmentSourceResolutionOutcome) (semantic.SourceOutcome, bool) {
	switch outcome {
	case runtimev1.AttachmentSourceResolutionOutcome_ATTACHMENT_SOURCE_RESOLUTION_OUTCOME_NOT_FOUND:
		return semantic.SourceNotFound, true
	case runtimev1.AttachmentSourceResolutionOutcome_ATTACHMENT_SOURCE_RESOLUTION_OUTCOME_UNAVAILABLE:
		return semantic.SourceUnavailable, true
	case runtimev1.AttachmentSourceResolutionOutcome_ATTACHMENT_SOURCE_RESOLUTION_OUTCOME_RESOLVED:
		return semantic.SourceResolved, true
	default:
		return "", false
	}
}

func recordAttachmentSourceInvalid(component semantic.InvalidComponent) *semantic.Failure {
	return &semantic.Failure{Category: semantic.FailureInvalidRequest, InvalidComponent: component}
}

func recordAttachmentSourceExhausted(kind semantic.LimitKind, maximum uint64) *semantic.Failure {
	return &semantic.Failure{Category: semantic.FailureResourceExhausted, LimitKind: kind, Maximum: maximum}
}

func recordAttachmentSourceInternal() *semantic.Failure {
	return &semantic.Failure{Category: semantic.FailureInternal}
}
