package runtimeapi

import (
	"context"
	"errors"
	"strings"

	"github.com/nexuscrm/shared/pkg/constants"
	sharedcontract "github.com/nexuscrm/shared/pkg/contract"
	"github.com/nexuscrm/shared/pkg/models"
	commonsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/commonv1"
	listviewsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/listviewv1"
	recordsemantic "github.com/pingcap-inc/nexuscrm-contract/pkg/runtime/recordsv1"
	runtimev1 "github.com/pingcap-inc/nexuscrm-contract/wire-go/gen/runtime/v1"
	"github.com/pingcap-inc/nexuscrm-contract/wire-go/mapping"
)

type PrivateListViewReader interface {
	ListPrivate(context.Context, *models.UserSession, string) ([]*models.ListView, error)
	DescribePrivate(context.Context, *models.UserSession, string, string) (*models.ListView, error)
}

type privateListViewPermissionEngine interface {
	CheckObjectPermissionWithUser(context.Context, string, string, *models.UserSession) bool
}

type PrivateListViewServer struct {
	runtimev1.UnimplementedPrivateListViewServiceServer
	reader      PrivateListViewReader
	authoring   sharedcontract.ListViewAuthoring
	permissions privateListViewPermissionEngine
}

func NewPrivateListViewServer(reader PrivateListViewReader, authoring sharedcontract.ListViewAuthoring, permissions privateListViewPermissionEngine) (*PrivateListViewServer, error) {
	if reader == nil || authoring == nil || permissions == nil {
		return nil, errors.New("runtime private list views require read, authoring, and permission engines")
	}
	return &PrivateListViewServer{reader: reader, authoring: authoring, permissions: permissions}, nil
}

func (s *PrivateListViewServer) ReadPrivateListViews(ctx context.Context, wireRequest *runtimev1.ReadPrivateListViewsRequest) (*runtimev1.ReadPrivateListViewsResponse, error) {
	request, err := mapping.FromWireReadPrivateListViewsRequest(ctx, wireRequest)
	if err != nil {
		return nil, mapping.ToPlatformStatusError(err, listviewsemantic.CapabilityName)
	}
	scope, scopeOK := runtimeExecutionScopeFromContext(ctx)
	view, viewOK := RuntimeMetadataViewFromContext(ctx)
	trusted, trustedOK := trustedCallFromContext(ctx)
	if !scopeOK || !viewOK || !trustedOK {
		return nil, privateListViewStatus(&recordsemantic.Failure{Category: recordsemantic.FailureUnavailable})
	}
	if err := requireCatalogRevision(request.MinCatalogRevision, scope.receipts.CatalogRevision); err != nil {
		return nil, privateListViewStatus(err)
	}
	if _, found := view.Object(request.ObjectID); !found {
		return nil, privateListViewStatus(&recordsemantic.Failure{Category: recordsemantic.FailureForbidden})
	}
	user := trusted.UserSession()
	if user.ID == "" || !s.permissions.CheckObjectPermissionWithUser(ctx, request.ObjectID, constants.PermRead, &user) {
		return nil, privateListViewStatus(&recordsemantic.Failure{Category: recordsemantic.FailureForbidden})
	}

	response := listviewsemantic.ReadResponse{Receipts: receiptsForCapability(scope.receipts, listviewsemantic.CapabilityName)}
	switch request.Selector {
	case listviewsemantic.ReadList:
		views, readErr := s.reader.ListPrivate(ctx, &user, request.ObjectID)
		if readErr != nil {
			return nil, privateListViewEngineStatus(ctx, readErr)
		}
		response.Views = make([]listviewsemantic.View, 0, len(views))
		for _, value := range views {
			projected, projectErr := privateListViewFromModel(value)
			if projectErr != nil {
				return nil, privateListViewStatus(projectErr)
			}
			response.Views = append(response.Views, projected)
		}
		listviewsemantic.SortViews(response.Views)
	case listviewsemantic.ReadDescribe:
		value, readErr := s.reader.DescribePrivate(ctx, &user, request.ObjectID, request.ViewID)
		if readErr != nil {
			return nil, privateListViewEngineStatus(ctx, readErr)
		}
		projected, projectErr := privateListViewFromModel(value)
		if projectErr != nil {
			return nil, privateListViewStatus(projectErr)
		}
		response.View = &projected
	default:
		return nil, privateListViewStatus(&recordsemantic.Failure{Category: recordsemantic.FailureInvalidRequest})
	}
	wireResponse, err := mapping.ToWireReadPrivateListViewsResponse(request, response)
	if err != nil {
		return nil, privateListViewStatus(err)
	}
	return wireResponse, nil
}

func (s *PrivateListViewServer) MutatePrivateListView(ctx context.Context, wireRequest *runtimev1.MutatePrivateListViewRequest) (*runtimev1.MutatePrivateListViewResponse, error) {
	request, err := mapping.FromWireMutatePrivateListViewRequest(ctx, wireRequest)
	if err != nil {
		return nil, privateListViewStatus(err)
	}
	scope, scopeOK := runtimeExecutionScopeFromContext(ctx)
	view, viewOK := RuntimeMetadataViewFromContext(ctx)
	trusted, trustedOK := trustedCallFromContext(ctx)
	if !scopeOK || !viewOK || !trustedOK {
		return nil, privateListViewStatus(&recordsemantic.Failure{Category: recordsemantic.FailureUnavailable})
	}
	if err := requireCatalogRevision(request.MinCatalogRevision, scope.receipts.CatalogRevision); err != nil {
		return nil, privateListViewStatus(err)
	}
	if _, found := view.Object(request.ObjectID); !found {
		return nil, privateListViewStatus(&recordsemantic.Failure{Category: recordsemantic.FailureForbidden})
	}
	user := trusted.UserSession()
	if user.ID == "" || !s.permissions.CheckObjectPermissionWithUser(ctx, request.ObjectID, constants.PermRead, &user) {
		return nil, privateListViewStatus(&recordsemantic.Failure{Category: recordsemantic.FailureForbidden})
	}

	response := listviewsemantic.MutateResponse{Receipts: receiptsForCapability(scope.receipts, listviewsemantic.CapabilityName)}
	switch request.Operation {
	case listviewsemantic.MutationCreate:
		input, inputErr := privateListViewCreateInput(request.ObjectID, request.Patch)
		if inputErr != nil {
			return nil, privateListViewStatus(inputErr)
		}
		created, createErr := s.authoring.CreatePrivate(ctx, &user, input)
		if createErr != nil {
			return nil, privateListViewEngineStatus(ctx, createErr)
		}
		projected, projectErr := privateListViewFromModel(created)
		if projectErr != nil {
			return nil, privateListViewStatus(projectErr)
		}
		response.View = &projected
	case listviewsemantic.MutationUpdate:
		if _, readErr := s.reader.DescribePrivate(ctx, &user, request.ObjectID, request.ViewID); readErr != nil {
			return nil, privateListViewEngineStatus(ctx, readErr)
		}
		patch, patchErr := privateListViewPatchFromSemantic(request.Patch)
		if patchErr != nil {
			return nil, privateListViewStatus(patchErr)
		}
		updated, updateErr := s.authoring.UpdatePrivate(ctx, &user, request.ViewID, patch)
		if updateErr != nil {
			return nil, privateListViewEngineStatus(ctx, updateErr)
		}
		projected, projectErr := privateListViewFromModel(updated)
		if projectErr != nil {
			return nil, privateListViewStatus(projectErr)
		}
		response.View = &projected
	case listviewsemantic.MutationDelete:
		if _, readErr := s.reader.DescribePrivate(ctx, &user, request.ObjectID, request.ViewID); readErr != nil {
			return nil, privateListViewEngineStatus(ctx, readErr)
		}
		if deleteErr := s.authoring.DeletePrivate(ctx, &user, request.ViewID); deleteErr != nil {
			return nil, privateListViewEngineStatus(ctx, deleteErr)
		}
	default:
		return nil, privateListViewStatus(&recordsemantic.Failure{Category: recordsemantic.FailureInvalidRequest})
	}
	if err := ctx.Err(); err != nil {
		return nil, privateListViewStatus(err)
	}
	wireResponse, err := mapping.ToWireMutatePrivateListViewResponse(request, response)
	if err != nil {
		return nil, privateListViewStatus(err)
	}
	return wireResponse, nil
}

func privateListViewCreateInput(objectID string, patch *listviewsemantic.Patch) (*models.ListView, error) {
	if patch == nil || patch.Label == nil {
		return nil, &recordsemantic.Failure{Category: recordsemantic.FailureInvalidRequest}
	}
	visibility := "Private"
	result := &models.ListView{ObjectAPIName: objectID, Label: *patch.Label, Visibility: &visibility}
	applyPrivateListViewPatch(result, patch)
	return result, nil
}

func privateListViewPatchFromSemantic(patch *listviewsemantic.Patch) (models.ListViewPatch, error) {
	if patch == nil {
		return models.ListViewPatch{}, &recordsemantic.Failure{Category: recordsemantic.FailureInvalidRequest}
	}
	result := models.ListViewPatch{
		Label: patch.Label, FilterExpr: patch.FilterExpression, FilterLogic: patch.FilterLogic,
		SortField: patch.SortFieldID, DeveloperName: patch.StableName,
	}
	if patch.Fields != nil {
		fields := append([]string(nil), patch.Fields.FieldIDs...)
		result.Fields = &fields
	}
	if patch.SortDirection != nil {
		direction, err := privateListViewSortToModel(*patch.SortDirection)
		if err != nil {
			return models.ListViewPatch{}, err
		}
		result.SortDirection = &direction
	}
	return result, nil
}

func applyPrivateListViewPatch(result *models.ListView, patch *listviewsemantic.Patch) {
	if result == nil || patch == nil {
		return
	}
	if patch.FilterExpression != nil {
		result.FilterExpr = *patch.FilterExpression
	}
	if patch.FilterLogic != nil {
		result.FilterLogic = *patch.FilterLogic
	}
	if patch.Fields != nil {
		result.Fields = append([]string(nil), patch.Fields.FieldIDs...)
	}
	if patch.SortFieldID != nil {
		result.SortField = *patch.SortFieldID
	}
	if patch.SortDirection != nil {
		result.SortDirection, _ = privateListViewSortToModel(*patch.SortDirection)
	}
	if patch.StableName != nil {
		value := *patch.StableName
		result.DeveloperName = &value
	}
}

func privateListViewFromModel(value *models.ListView) (listviewsemantic.View, error) {
	if value == nil || value.DeveloperName == nil {
		return listviewsemantic.View{}, &recordsemantic.Failure{Category: recordsemantic.FailureInternal}
	}
	direction, err := privateListViewSortFromModel(value.SortDirection)
	if err != nil {
		return listviewsemantic.View{}, err
	}
	return listviewsemantic.View{
		ViewID: value.ID, ObjectID: value.ObjectAPIName, Label: value.Label,
		FilterExpression: value.FilterExpr, FilterLogic: value.FilterLogic,
		FieldIDs: append([]string(nil), value.Fields...), SortFieldID: value.SortField,
		SortDirection: direction, StableName: strings.TrimSpace(*value.DeveloperName),
	}, nil
}

func privateListViewSortFromModel(value string) (listviewsemantic.SortDirection, error) {
	switch strings.ToUpper(strings.TrimSpace(value)) {
	case "":
		return "", nil
	case "ASC":
		return listviewsemantic.SortAscending, nil
	case "DESC":
		return listviewsemantic.SortDescending, nil
	default:
		return "", &recordsemantic.Failure{Category: recordsemantic.FailureInternal}
	}
}

func privateListViewSortToModel(value listviewsemantic.SortDirection) (string, error) {
	switch value {
	case "":
		return "", nil
	case listviewsemantic.SortAscending:
		return "ASC", nil
	case listviewsemantic.SortDescending:
		return "DESC", nil
	default:
		return "", &recordsemantic.Failure{Category: recordsemantic.FailureInvalidRequest}
	}
}

func privateListViewEngineStatus(ctx context.Context, err error) error {
	return privateListViewStatus(classifyPlatformEngineError(ctx, err, commonsemantic.InvalidComponentCommandData))
}

func privateListViewStatus(err error) error {
	return mapping.ToPlatformStatusError(err, listviewsemantic.CapabilityName)
}
