package mapping

import (
	"context"
	"errors"

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

func ToWireReadRecordViewRequest(ctx context.Context, request semantic.ReadRequest) (*runtimev1.ReadRecordViewRequest, error) {
	if err := semantic.ValidateReadRequest(ctx, request); err != nil {
		return nil, err
	}
	result := &runtimev1.ReadRecordViewRequest{
		ObjectId: request.ObjectID,
		Selector: &runtimev1.RecordViewSelector{
			Kind: toWireRecordViewSelectorKind(request.Selector.Kind), RecordId: request.Selector.RecordID,
			FieldId: request.Selector.FieldID, FieldValue: request.Selector.FieldValue,
		},
		Projection: toWireRecordViewProjection(request.Projection), MinCatalogRevision: request.MinCatalogRevision,
	}
	if request.Authority != nil {
		result.Workspace = &runtimev1.RecordViewWorkspaceSelection{}
		for _, section := range request.Authority.Sections {
			result.Workspace.Sections = append(result.Workspace.Sections, toWireRecordViewWorkspaceSection(section))
		}
	}
	return result, nil
}

func FromWireReadRecordViewRequest(ctx context.Context, request *runtimev1.ReadRecordViewRequest) (semantic.ReadRequest, error) {
	if request == nil || request.GetSelector() == nil {
		return semantic.ReadRequest{}, recordViewInvalid(semantic.InvalidComponentResponse)
	}
	selectorKind, ok := fromWireRecordViewSelectorKind(request.GetSelector().GetKind())
	if !ok {
		return semantic.ReadRequest{}, recordViewInvalid(semantic.InvalidComponentOperation)
	}
	projection, ok := fromWireRecordViewProjection(request.GetProjection())
	if !ok {
		return semantic.ReadRequest{}, recordViewInvalid(semantic.InvalidComponentOperation)
	}
	result := semantic.ReadRequest{
		ObjectID:   request.GetObjectId(),
		Selector:   semantic.Selector{Kind: selectorKind, RecordID: request.GetSelector().GetRecordId(), FieldID: request.GetSelector().GetFieldId(), FieldValue: request.GetSelector().GetFieldValue()},
		Projection: projection, MinCatalogRevision: request.MinCatalogRevision,
	}
	if workspace := request.GetWorkspace(); workspace != nil {
		result.Authority = &semantic.AuthoritySelection{}
		for _, wireSection := range workspace.GetSections() {
			section, ok := fromWireRecordViewWorkspaceSection(wireSection)
			if !ok {
				return semantic.ReadRequest{}, recordViewInvalid(semantic.InvalidComponentOperation)
			}
			result.Authority.Sections = append(result.Authority.Sections, section)
		}
	}
	if err := semantic.ValidateReadRequest(ctx, result); err != nil {
		return semantic.ReadRequest{}, err
	}
	return result, nil
}

func ToWireReadRecordViewResponse(request semantic.ReadRequest, response semantic.ReadResponse) (*runtimev1.ReadRecordViewResponse, error) {
	if err := semantic.ValidateReadResponse(request, response); err != nil {
		return nil, err
	}
	root, err := toWireQueryRecord(response.Root)
	if err != nil {
		return nil, recordViewInternal()
	}
	displays := make([]*runtimev1.RecordViewReferenceDisplay, 0, len(response.ReferenceDisplays))
	for _, display := range response.ReferenceDisplays {
		displays = append(displays, &runtimev1.RecordViewReferenceDisplay{
			SourceFieldId: display.SourceFieldID, RelationshipId: display.RelationshipID,
			TargetObjectId: display.TargetObjectID, TargetRecordId: display.TargetRecordID, DisplayName: display.DisplayName,
		})
	}
	attachmentFields := make([]*runtimev1.RecordViewAttachmentSourceField, 0, len(response.AttachmentSourceFields))
	for _, field := range response.AttachmentSourceFields {
		attachmentFields = append(attachmentFields, &runtimev1.RecordViewAttachmentSourceField{
			SourceFieldId: field.SourceFieldID, ResolverId: field.ResolverID,
		})
	}
	result := &runtimev1.ReadRecordViewResponse{
		RecordId: response.RecordID, Root: root, ReferenceDisplays: displays,
		AttachmentSourceFields: attachmentFields, Receipts: toWireReceipts(response.Receipts, recordViewCapability),
		Access: toWireRecordViewAccess(response.Access),
	}
	if response.Authority != nil {
		result.Authority = toWireRecordViewSelectedAuthority(response.Authority)
	}
	return result, nil
}

func toWireRecordViewAccess(access *semantic.RecordAccess) *runtimev1.RecordViewAccess {
	if access == nil {
		return nil
	}
	return &runtimev1.RecordViewAccess{Read: access.Read, Update: access.Update, Delete: access.Delete, Locked: access.Locked}
}

func FromWireReadRecordViewResponse(request semantic.ReadRequest, response *runtimev1.ReadRecordViewResponse) (semantic.ReadResponse, error) {
	if response == nil || response.GetRoot() == nil {
		return semantic.ReadResponse{}, recordViewInternal()
	}
	if len(response.GetRoot().GetFields()) > semantic.MaxRootFields {
		return semantic.ReadResponse{}, recordViewExhausted(semantic.LimitFieldCount, semantic.MaxRootFields)
	}
	if len(response.GetReferenceDisplays()) > semantic.MaxReferenceDisplays {
		return semantic.ReadResponse{}, recordViewExhausted(semantic.LimitFieldCount, semantic.MaxReferenceDisplays)
	}
	if len(response.GetAttachmentSourceFields()) > semantic.MaxAttachmentSources {
		return semantic.ReadResponse{}, recordViewExhausted(semantic.LimitOperationCount, semantic.MaxAttachmentSources)
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), recordViewCapability)
	if err != nil {
		return semantic.ReadResponse{}, err
	}
	root, err := fromWireQueryRecord(response.GetRoot(), 1)
	if err != nil {
		var failure *semantic.Failure
		if errors.As(err, &failure) && failure.Category == semantic.FailureResourceExhausted && validRecordViewFailure(failure, false) {
			return semantic.ReadResponse{}, failure
		}
		return semantic.ReadResponse{}, recordViewInternal()
	}
	result := semantic.ReadResponse{RecordID: response.GetRecordId(), Root: root, Receipts: receipts}
	if access := response.GetAccess(); access != nil {
		result.Access = &semantic.RecordAccess{Read: access.GetRead(), Update: access.GetUpdate(), Delete: access.GetDelete(), Locked: access.GetLocked()}
	}
	if authority := response.GetAuthority(); authority != nil {
		selected, err := fromWireRecordViewSelectedAuthority(authority)
		if err != nil {
			return semantic.ReadResponse{}, err
		}
		result.Authority = selected
	}
	for _, display := range response.GetReferenceDisplays() {
		if display == nil {
			return semantic.ReadResponse{}, recordViewInternal()
		}
		result.ReferenceDisplays = append(result.ReferenceDisplays, semantic.ReferenceDisplay{
			SourceFieldID: display.GetSourceFieldId(), RelationshipID: display.GetRelationshipId(),
			TargetObjectID: display.GetTargetObjectId(), TargetRecordID: display.GetTargetRecordId(), DisplayName: display.GetDisplayName(),
		})
	}
	for _, field := range response.GetAttachmentSourceFields() {
		if field == nil {
			return semantic.ReadResponse{}, recordViewInternal()
		}
		result.AttachmentSourceFields = append(result.AttachmentSourceFields, semantic.AttachmentSourceField{
			SourceFieldID: field.GetSourceFieldId(), ResolverID: field.GetResolverId(),
		})
	}
	if err := semantic.ValidateReadResponse(request, result); err != nil {
		return semantic.ReadResponse{}, err
	}
	return result, nil
}

func toWireRecordViewSelectedAuthority(source *semantic.SelectedAuthority) *runtimev1.RecordViewSelectedAuthority {
	result := &runtimev1.RecordViewSelectedAuthority{}
	for _, permission := range source.SystemPermissions {
		result.SystemPermissions = append(result.SystemPermissions, &runtimev1.EffectivePermissionGrant{PermissionId: permission.PermissionID, Granted: permission.Granted})
	}
	for _, permission := range source.CustomPermissions {
		result.CustomPermissions = append(result.CustomPermissions, &runtimev1.EffectivePermissionGrant{PermissionId: permission.PermissionID, Granted: permission.Granted})
	}
	for _, permission := range source.ObjectPermissions {
		result.ObjectPermissions = append(result.ObjectPermissions, &runtimev1.EffectiveObjectPermission{
			ResourceType: permission.ResourceType, CanRead: permission.CanRead, CanCreate: permission.CanCreate,
			CanEdit: permission.CanEdit, CanDelete: permission.CanDelete, CanDeactivate: permission.CanDeactivate,
			CanViewAll: permission.CanViewAll, CanModifyAll: permission.CanModifyAll,
		})
	}
	for _, permission := range source.FieldPermissions {
		result.FieldPermissions = append(result.FieldPermissions, &runtimev1.EffectiveFieldPermission{
			ResourceType: permission.ResourceType, FieldName: permission.FieldName, CanRead: permission.CanRead, CanEdit: permission.CanEdit, CanCreate: permission.CanCreate,
		})
	}
	for _, permission := range source.RecordTypePermissions {
		result.RecordTypePermissions = append(result.RecordTypePermissions, &runtimev1.EffectiveRecordTypePermission{
			ResourceType: permission.ResourceType, RecordTypeId: permission.RecordTypeID,
			Available: permission.Available, IsDefault: permission.IsDefault,
		})
	}
	return result
}

func fromWireRecordViewSelectedAuthority(source *runtimev1.RecordViewSelectedAuthority) (*semantic.SelectedAuthority, error) {
	result := &semantic.SelectedAuthority{}
	for _, permission := range source.GetSystemPermissions() {
		if permission == nil {
			return nil, recordViewInternal()
		}
		result.SystemPermissions = append(result.SystemPermissions, permissionsemantic.PermissionGrant{PermissionID: permission.GetPermissionId(), Granted: permission.GetGranted()})
	}
	for _, permission := range source.GetCustomPermissions() {
		if permission == nil {
			return nil, recordViewInternal()
		}
		result.CustomPermissions = append(result.CustomPermissions, permissionsemantic.PermissionGrant{PermissionID: permission.GetPermissionId(), Granted: permission.GetGranted()})
	}
	for _, permission := range source.GetObjectPermissions() {
		if permission == nil {
			return nil, recordViewInternal()
		}
		result.ObjectPermissions = append(result.ObjectPermissions, permissionsemantic.ObjectPermission{
			ResourceType: permission.GetResourceType(), CanRead: permission.GetCanRead(), CanCreate: permission.GetCanCreate(),
			CanEdit: permission.GetCanEdit(), CanDelete: permission.GetCanDelete(), CanDeactivate: permission.GetCanDeactivate(),
			CanViewAll: permission.GetCanViewAll(), CanModifyAll: permission.GetCanModifyAll(),
		})
	}
	for _, permission := range source.GetFieldPermissions() {
		if permission == nil {
			return nil, recordViewInternal()
		}
		result.FieldPermissions = append(result.FieldPermissions, permissionsemantic.FieldPermission{
			ResourceType: permission.GetResourceType(), FieldName: permission.GetFieldName(), CanRead: permission.GetCanRead(), CanEdit: permission.GetCanEdit(), CanCreate: permission.GetCanCreate(),
		})
	}
	for _, permission := range source.GetRecordTypePermissions() {
		if permission == nil {
			return nil, recordViewInternal()
		}
		result.RecordTypePermissions = append(result.RecordTypePermissions, permissionsemantic.RecordTypePermission{
			ResourceType: permission.GetResourceType(), RecordTypeID: permission.GetRecordTypeId(),
			Available: permission.GetAvailable(), IsDefault: permission.GetIsDefault(),
		})
	}
	return result, nil
}

func toWireRecordViewWorkspaceSection(section semantic.AuthoritySection) runtimev1.RecordViewWorkspaceSection {
	switch section {
	case semantic.AuthoritySystemPermissions:
		return runtimev1.RecordViewWorkspaceSection_RECORD_VIEW_WORKSPACE_SECTION_SYSTEM_PERMISSIONS
	case semantic.AuthorityCustomPermissions:
		return runtimev1.RecordViewWorkspaceSection_RECORD_VIEW_WORKSPACE_SECTION_CUSTOM_PERMISSIONS
	case semantic.AuthorityObjectPermissions:
		return runtimev1.RecordViewWorkspaceSection_RECORD_VIEW_WORKSPACE_SECTION_OBJECT_PERMISSIONS
	case semantic.AuthorityFieldPermissions:
		return runtimev1.RecordViewWorkspaceSection_RECORD_VIEW_WORKSPACE_SECTION_FIELD_PERMISSIONS
	case semantic.AuthorityRecordTypePermissions:
		return runtimev1.RecordViewWorkspaceSection_RECORD_VIEW_WORKSPACE_SECTION_RECORD_TYPE_PERMISSIONS
	default:
		return runtimev1.RecordViewWorkspaceSection_RECORD_VIEW_WORKSPACE_SECTION_UNSPECIFIED
	}
}

func fromWireRecordViewWorkspaceSection(section runtimev1.RecordViewWorkspaceSection) (semantic.AuthoritySection, bool) {
	switch section {
	case runtimev1.RecordViewWorkspaceSection_RECORD_VIEW_WORKSPACE_SECTION_SYSTEM_PERMISSIONS:
		return semantic.AuthoritySystemPermissions, true
	case runtimev1.RecordViewWorkspaceSection_RECORD_VIEW_WORKSPACE_SECTION_CUSTOM_PERMISSIONS:
		return semantic.AuthorityCustomPermissions, true
	case runtimev1.RecordViewWorkspaceSection_RECORD_VIEW_WORKSPACE_SECTION_OBJECT_PERMISSIONS:
		return semantic.AuthorityObjectPermissions, true
	case runtimev1.RecordViewWorkspaceSection_RECORD_VIEW_WORKSPACE_SECTION_FIELD_PERMISSIONS:
		return semantic.AuthorityFieldPermissions, true
	case runtimev1.RecordViewWorkspaceSection_RECORD_VIEW_WORKSPACE_SECTION_RECORD_TYPE_PERMISSIONS:
		return semantic.AuthorityRecordTypePermissions, true
	default:
		return "", false
	}
}

func ToWireResolveRecordAttachmentSourcesRequest(ctx context.Context, request semantic.ResolveAttachmentSourcesRequest) (*runtimev1.ResolveRecordAttachmentSourcesRequest, error) {
	if err := semantic.ValidateResolveRequest(ctx, request); err != nil {
		return nil, err
	}
	return &runtimev1.ResolveRecordAttachmentSourcesRequest{
		ParentObjectId: request.ParentObjectID, ParentRecordId: request.ParentRecordID,
		ResolverId: request.ResolverID, SourceValues: append([]string(nil), request.SourceValues...), MinCatalogRevision: request.MinCatalogRevision,
	}, nil
}

func FromWireResolveRecordAttachmentSourcesRequest(ctx context.Context, request *runtimev1.ResolveRecordAttachmentSourcesRequest) (semantic.ResolveAttachmentSourcesRequest, error) {
	if request == nil {
		return semantic.ResolveAttachmentSourcesRequest{}, recordViewInvalid(semantic.InvalidComponentResponse)
	}
	if len(request.GetSourceValues()) > semantic.MaxAttachmentSources {
		return semantic.ResolveAttachmentSourcesRequest{}, recordViewExhausted(semantic.LimitOperationCount, semantic.MaxAttachmentSources)
	}
	result := semantic.ResolveAttachmentSourcesRequest{
		ParentObjectID: request.GetParentObjectId(), ParentRecordID: request.GetParentRecordId(), ResolverID: request.GetResolverId(),
		SourceValues: append([]string(nil), request.GetSourceValues()...), MinCatalogRevision: request.MinCatalogRevision,
	}
	if err := semantic.ValidateResolveRequest(ctx, result); err != nil {
		return semantic.ResolveAttachmentSourcesRequest{}, err
	}
	return result, nil
}

func ToWireResolveRecordAttachmentSourcesResponse(request semantic.ResolveAttachmentSourcesRequest, response semantic.ResolveAttachmentSourcesResponse) (*runtimev1.ResolveRecordAttachmentSourcesResponse, error) {
	if err := semantic.ValidateResolveResponse(request, response); err != nil {
		return nil, err
	}
	results := make([]*runtimev1.RecordAttachmentSourceResult, 0, len(response.Results))
	for _, result := range response.Results {
		results = append(results, &runtimev1.RecordAttachmentSourceResult{
			Index: result.Index, Outcome: toWireAttachmentSourceOutcome(result.Outcome), FileId: result.FileID, FileName: result.FileName,
		})
	}
	return &runtimev1.ResolveRecordAttachmentSourcesResponse{Results: results, Receipts: toWireReceipts(response.Receipts, recordViewCapability)}, nil
}

func FromWireResolveRecordAttachmentSourcesResponse(request semantic.ResolveAttachmentSourcesRequest, response *runtimev1.ResolveRecordAttachmentSourcesResponse) (semantic.ResolveAttachmentSourcesResponse, error) {
	if response == nil || len(response.GetResults()) != len(request.SourceValues) || len(response.GetResults()) > semantic.MaxAttachmentSources {
		return semantic.ResolveAttachmentSourcesResponse{}, recordViewInternal()
	}
	receipts, err := fromWireReceipts(response.GetReceipts(), recordViewCapability)
	if err != nil {
		return semantic.ResolveAttachmentSourcesResponse{}, err
	}
	result := semantic.ResolveAttachmentSourcesResponse{Receipts: receipts}
	for _, wireResult := range response.GetResults() {
		if wireResult == nil {
			return semantic.ResolveAttachmentSourcesResponse{}, recordViewInternal()
		}
		outcome, ok := fromWireAttachmentSourceOutcome(wireResult.GetOutcome())
		if !ok {
			return semantic.ResolveAttachmentSourcesResponse{}, recordViewInternal()
		}
		result.Results = append(result.Results, semantic.AttachmentSourceResult{
			Index: wireResult.GetIndex(), Outcome: outcome, FileID: wireResult.GetFileId(), FileName: wireResult.GetFileName(),
		})
	}
	if err := semantic.ValidateResolveResponse(request, result); err != nil {
		return semantic.ResolveAttachmentSourcesResponse{}, err
	}
	return result, nil
}

func toWireRecordViewSelectorKind(kind semantic.SelectorKind) runtimev1.RecordViewSelectorKind {
	switch kind {
	case semantic.SelectorRecordID:
		return runtimev1.RecordViewSelectorKind_RECORD_VIEW_SELECTOR_KIND_RECORD_ID
	case semantic.SelectorFieldEqual:
		return runtimev1.RecordViewSelectorKind_RECORD_VIEW_SELECTOR_KIND_FIELD_EQUAL
	default:
		return runtimev1.RecordViewSelectorKind_RECORD_VIEW_SELECTOR_KIND_UNSPECIFIED
	}
}

func fromWireRecordViewSelectorKind(kind runtimev1.RecordViewSelectorKind) (semantic.SelectorKind, bool) {
	switch kind {
	case runtimev1.RecordViewSelectorKind_RECORD_VIEW_SELECTOR_KIND_RECORD_ID:
		return semantic.SelectorRecordID, true
	case runtimev1.RecordViewSelectorKind_RECORD_VIEW_SELECTOR_KIND_FIELD_EQUAL:
		return semantic.SelectorFieldEqual, true
	default:
		return "", false
	}
}

func toWireRecordViewProjection(projection semantic.Projection) runtimev1.RecordViewProjection {
	switch projection {
	case semantic.ProjectionRoot:
		return runtimev1.RecordViewProjection_RECORD_VIEW_PROJECTION_ROOT
	case semantic.ProjectionEnrichedView:
		return runtimev1.RecordViewProjection_RECORD_VIEW_PROJECTION_ENRICHED_VIEW
	default:
		return runtimev1.RecordViewProjection_RECORD_VIEW_PROJECTION_UNSPECIFIED
	}
}

func fromWireRecordViewProjection(projection runtimev1.RecordViewProjection) (semantic.Projection, bool) {
	switch projection {
	case runtimev1.RecordViewProjection_RECORD_VIEW_PROJECTION_ROOT:
		return semantic.ProjectionRoot, true
	case runtimev1.RecordViewProjection_RECORD_VIEW_PROJECTION_ENRICHED_VIEW:
		return semantic.ProjectionEnrichedView, true
	default:
		return "", false
	}
}

func toWireAttachmentSourceOutcome(outcome semantic.AttachmentSourceOutcome) runtimev1.RecordAttachmentSourceOutcome {
	switch outcome {
	case semantic.AttachmentSourceNotFound:
		return runtimev1.RecordAttachmentSourceOutcome_RECORD_ATTACHMENT_SOURCE_OUTCOME_NOT_FOUND
	case semantic.AttachmentSourceUnavailable:
		return runtimev1.RecordAttachmentSourceOutcome_RECORD_ATTACHMENT_SOURCE_OUTCOME_UNAVAILABLE
	case semantic.AttachmentSourceResolved:
		return runtimev1.RecordAttachmentSourceOutcome_RECORD_ATTACHMENT_SOURCE_OUTCOME_RESOLVED
	default:
		return runtimev1.RecordAttachmentSourceOutcome_RECORD_ATTACHMENT_SOURCE_OUTCOME_UNSPECIFIED
	}
}

func fromWireAttachmentSourceOutcome(outcome runtimev1.RecordAttachmentSourceOutcome) (semantic.AttachmentSourceOutcome, bool) {
	switch outcome {
	case runtimev1.RecordAttachmentSourceOutcome_RECORD_ATTACHMENT_SOURCE_OUTCOME_NOT_FOUND:
		return semantic.AttachmentSourceNotFound, true
	case runtimev1.RecordAttachmentSourceOutcome_RECORD_ATTACHMENT_SOURCE_OUTCOME_UNAVAILABLE:
		return semantic.AttachmentSourceUnavailable, true
	case runtimev1.RecordAttachmentSourceOutcome_RECORD_ATTACHMENT_SOURCE_OUTCOME_RESOLVED:
		return semantic.AttachmentSourceResolved, true
	default:
		return "", false
	}
}

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

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

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