package mapping

import (
	"context"

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

func ToWireListRecycleBinRequest(ctx context.Context, request semantic.Request) (*runtimev1.ListRecycleBinRequest, error) {
	if err := semantic.ValidateRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.ListRecycleBinRequest{
		Scope:              recycleBinScopeToWire(request.Scope),
		MinCatalogRevision: request.MinCatalogRevision,
	}, nil
}

func FromWireListRecycleBinRequest(ctx context.Context, request *runtimev1.ListRecycleBinRequest) (semantic.Request, error) {
	if request == nil {
		return semantic.Request{}, recycleBinInvalid(semantic.InvalidComponentResponse)
	}
	scope, ok := recycleBinScopeFromWire(request.GetScope())
	if !ok {
		return semantic.Request{}, recycleBinInvalid(semantic.InvalidComponentOperation)
	}
	result := semantic.Request{Scope: scope, MinCatalogRevision: request.MinCatalogRevision}
	if err := semantic.ValidateRequest(ctx, result); err != nil {
		return semantic.Request{}, err
	}
	return result, nil
}

func ToWireListRecycleBinResponse(response semantic.Response) (*runtimev1.ListRecycleBinResponse, error) {
	if err := semantic.ValidateResponse(response); err != nil {
		return nil, err
	}
	result := &runtimev1.ListRecycleBinResponse{
		Items:         make([]*runtimev1.RecycleBinItem, 0, len(response.Items)),
		FailedObjects: make([]*runtimev1.RecycleBinObjectFailure, 0, len(response.FailedObjects)),
		Receipts:      toWireReceipts(response.Receipts, recycleBinCapability),
	}
	for _, item := range response.Items {
		result.Items = append(result.Items, &runtimev1.RecycleBinItem{
			RecordId: item.RecordID, RecordName: item.RecordName, ObjectId: item.ObjectID,
			DeletedBy: item.DeletedBy, DeletedDate: item.DeletedDate,
			CreatedDate: item.CreatedDate, LastModifiedDate: item.LastModifiedDate,
		})
	}
	for _, failure := range response.FailedObjects {
		result.FailedObjects = append(result.FailedObjects, &runtimev1.RecycleBinObjectFailure{
			ObjectId: failure.ObjectID, Code: recycleBinObjectFailureCodeToWire(failure.Code), Message: failure.Message,
		})
	}
	return result, nil
}

func FromWireListRecycleBinResponse(response *runtimev1.ListRecycleBinResponse) (semantic.Response, error) {
	if response == nil {
		return semantic.Response{}, recycleBinIncompatible()
	}
	if len(response.GetItems()) > semantic.MaxItems || len(response.GetFailedObjects()) > semantic.MaxFailedObjects {
		return semantic.Response{}, recycleBinInternal()
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), recycleBinCapability)
	if err != nil {
		return semantic.Response{}, err
	}
	result := semantic.Response{
		Items:         make([]semantic.Item, 0, len(response.GetItems())),
		FailedObjects: make([]semantic.ObjectFailure, 0, len(response.GetFailedObjects())), Receipts: receipts,
	}
	for _, item := range response.GetItems() {
		if item == nil {
			return semantic.Response{}, recycleBinInternal()
		}
		result.Items = append(result.Items, semantic.Item{
			RecordID: item.GetRecordId(), RecordName: item.GetRecordName(), ObjectID: item.GetObjectId(),
			DeletedBy: item.GetDeletedBy(), DeletedDate: item.GetDeletedDate(),
			CreatedDate: item.GetCreatedDate(), LastModifiedDate: item.GetLastModifiedDate(),
		})
	}
	for _, failure := range response.GetFailedObjects() {
		if failure == nil {
			return semantic.Response{}, recycleBinInternal()
		}
		code, ok := recycleBinObjectFailureCodeFromWire(failure.GetCode())
		if !ok {
			return semantic.Response{}, recycleBinInternal()
		}
		result.FailedObjects = append(result.FailedObjects, semantic.ObjectFailure{
			ObjectID: failure.GetObjectId(), Code: code, Message: failure.GetMessage(),
		})
	}
	if err := semantic.ValidateResponse(result); err != nil {
		return semantic.Response{}, err
	}
	return result, nil
}

func recycleBinScopeToWire(scope semantic.Scope) runtimev1.RecycleBinScope {
	switch scope {
	case semantic.ScopeMine:
		return runtimev1.RecycleBinScope_RECYCLE_BIN_SCOPE_MINE
	case semantic.ScopeAll:
		return runtimev1.RecycleBinScope_RECYCLE_BIN_SCOPE_ALL
	default:
		return runtimev1.RecycleBinScope_RECYCLE_BIN_SCOPE_UNSPECIFIED
	}
}

func recycleBinScopeFromWire(scope runtimev1.RecycleBinScope) (semantic.Scope, bool) {
	switch scope {
	case runtimev1.RecycleBinScope_RECYCLE_BIN_SCOPE_MINE:
		return semantic.ScopeMine, true
	case runtimev1.RecycleBinScope_RECYCLE_BIN_SCOPE_ALL:
		return semantic.ScopeAll, true
	default:
		return "", false
	}
}

func recycleBinObjectFailureCodeToWire(code semantic.ObjectFailureCode) runtimev1.RecycleBinObjectFailureCode {
	switch code {
	case semantic.ObjectFailureUnknown:
		return runtimev1.RecycleBinObjectFailureCode_RECYCLE_BIN_OBJECT_FAILURE_CODE_UNKNOWN
	case semantic.ObjectFailureForbidden:
		return runtimev1.RecycleBinObjectFailureCode_RECYCLE_BIN_OBJECT_FAILURE_CODE_FORBIDDEN
	case semantic.ObjectFailureInvalid:
		return runtimev1.RecycleBinObjectFailureCode_RECYCLE_BIN_OBJECT_FAILURE_CODE_INVALID_REQUEST
	case semantic.ObjectFailureInternal:
		return runtimev1.RecycleBinObjectFailureCode_RECYCLE_BIN_OBJECT_FAILURE_CODE_INTERNAL
	default:
		return runtimev1.RecycleBinObjectFailureCode_RECYCLE_BIN_OBJECT_FAILURE_CODE_UNSPECIFIED
	}
}

func recycleBinObjectFailureCodeFromWire(code runtimev1.RecycleBinObjectFailureCode) (semantic.ObjectFailureCode, bool) {
	switch code {
	case runtimev1.RecycleBinObjectFailureCode_RECYCLE_BIN_OBJECT_FAILURE_CODE_UNKNOWN:
		return semantic.ObjectFailureUnknown, true
	case runtimev1.RecycleBinObjectFailureCode_RECYCLE_BIN_OBJECT_FAILURE_CODE_FORBIDDEN:
		return semantic.ObjectFailureForbidden, true
	case runtimev1.RecycleBinObjectFailureCode_RECYCLE_BIN_OBJECT_FAILURE_CODE_INVALID_REQUEST:
		return semantic.ObjectFailureInvalid, true
	case runtimev1.RecycleBinObjectFailureCode_RECYCLE_BIN_OBJECT_FAILURE_CODE_INTERNAL:
		return semantic.ObjectFailureInternal, true
	default:
		return "", false
	}
}

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

func recycleBinIncompatible() *semantic.Failure {
	return &semantic.Failure{Category: semantic.FailureIncompatible, RequiredCapability: semantic.CapabilityName, SupportedMajor: semantic.ProtocolMajor, SupportedMinorMin: semantic.ProtocolMinor, SupportedMinorMax: semantic.ProtocolMinor}
}

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