package server

import (
	"context"
	"slices"

	"go.flipt.io/flipt/errors"
	"go.flipt.io/flipt/internal/server/authz"
	"go.flipt.io/flipt/internal/storage"
	flipt "go.flipt.io/flipt/rpc/flipt"
	"go.uber.org/zap"
	empty "google.golang.org/protobuf/types/known/emptypb"
)

// GetNamespace gets a namespace
func (s *Server) GetNamespace(ctx context.Context, r *flipt.GetNamespaceRequest) (*flipt.Namespace, error) {
	s.logger.Debug("get namespace", zap.Stringer("request", r))
	namespace, err := s.store.GetNamespace(ctx, storage.NewNamespace(r.Key, storage.WithReference(r.Reference)))
	s.logger.Debug("get namespace", zap.Stringer("response", namespace))
	return namespace, err
}

// ListNamespaces lists all namespaces
func (s *Server) ListNamespaces(ctx context.Context, r *flipt.ListNamespaceRequest) (*flipt.NamespaceList, error) {
	s.logger.Debug("list namespaces", zap.Stringer("request", r))

	ref := storage.ReferenceRequest{Reference: storage.Reference(r.Reference)}
	results, err := s.store.ListNamespaces(ctx, storage.ListWithParameters(ref, r))
	if err != nil {
		return nil, err
	}

	namespaces := results.Results

	total, err := s.store.CountNamespaces(ctx, ref)
	if err != nil {
		return nil, err
	}

	viewableNamespaces, ok := ctx.Value(authz.NamespacesKey).([]string)
	if viewableNamespaces != nil && ok {
		filtered := make([]*flipt.Namespace, 0)
		for _, n := range namespaces {
			if slices.Contains(viewableNamespaces, n.Key) {
				filtered = append(filtered, n)
			}
		}
		namespaces = filtered
		total = uint64(len(filtered))
	}

	resp := flipt.NamespaceList{
		Namespaces:    namespaces,
		TotalCount:    int32(total),
		NextPageToken: results.NextPageToken,
	}

	s.logger.Debug("list namespaces", zap.Stringer("response", &resp))
	return &resp, nil
}

// CreateNamespace creates a namespace
func (s *Server) CreateNamespace(ctx context.Context, r *flipt.CreateNamespaceRequest) (*flipt.Namespace, error) {
	s.logger.Debug("create namespace", zap.Stringer("request", r))
	namespace, err := s.store.CreateNamespace(ctx, r)
	s.logger.Debug("create namespace", zap.Stringer("response", namespace))
	return namespace, err
}

// UpdateNamespace updates an existing namespace
func (s *Server) UpdateNamespace(ctx context.Context, r *flipt.UpdateNamespaceRequest) (*flipt.Namespace, error) {
	s.logger.Debug("update namespace", zap.Stringer("request", r))
	namespace, err := s.store.UpdateNamespace(ctx, r)
	s.logger.Debug("update namespace", zap.Stringer("response", namespace))
	return namespace, err
}

// DeleteNamespace deletes a namespace
func (s *Server) DeleteNamespace(ctx context.Context, r *flipt.DeleteNamespaceRequest) (*empty.Empty, error) {
	s.logger.Debug("delete namespace", zap.Stringer("request", r))
	namespace, err := s.store.GetNamespace(ctx, storage.NewNamespace(r.Key))
	if err != nil {
		return nil, err
	}

	// if namespace is not found then nothing to do
	if namespace == nil {
		return &empty.Empty{}, nil
	}

	if !r.GetForce() && namespace.Protected {
		return nil, errors.ErrInvalidf("namespace %q is protected", r.Key)
	}

	if !r.GetForce() {
		// if any flags exist under the namespace then it cannot be deleted
		count, err := s.store.CountFlags(ctx, storage.NewNamespace(r.Key))
		if err != nil {
			return nil, err
		}

		if count > 0 {
			return nil, errors.ErrInvalidf("namespace %q cannot be deleted; flags must be deleted first", r.Key)
		}
	}

	if err := s.store.DeleteNamespace(ctx, r); err != nil {
		return nil, err
	}

	return &empty.Empty{}, nil
}
