// Licensed to Apache Software Foundation (ASF) under one or more contributor
// license agreements. See the NOTICE file distributed with
// this work for additional information regarding copyright
// ownership. Apache Software Foundation (ASF) licenses this file to you under
// the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package cmd

import (
	"fmt"
	"io"

	"github.com/go-resty/resty/v2"
	"github.com/spf13/cobra"
	"google.golang.org/protobuf/encoding/protojson"

	commonv1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/common/v1"
	databasev1 "github.com/apache/skywalking-banyandb/api/proto/banyandb/database/v1"
	"github.com/apache/skywalking-banyandb/pkg/version"
)

func newGroupCmd() *cobra.Command {
	groupCmd := &cobra.Command{
		Use:     "group",
		Version: version.Build(),
		Short:   "Group operation",
	}

	createCmd := &cobra.Command{
		Use:     "create -f [file|dir|-]",
		Version: version.Build(),
		Short:   "Create groups from files",
		RunE: func(cmd *cobra.Command, _ []string) (err error) {
			return rest(cmd.OutOrStdout(), func() ([]reqBody, error) { return parseNameFromYAML(cmd.InOrStdin()) },
				func(request request) (*resty.Response, error) {
					g := new(commonv1.Group)
					err := protojson.Unmarshal(request.data, g)
					if err != nil {
						return nil, err
					}
					cr := &databasev1.GroupRegistryServiceCreateRequest{
						Group: g,
					}
					b, err := protojson.Marshal(cr)
					if err != nil {
						return nil, err
					}
					return request.req.SetBody(b).Post(getPath("/api/v1/group/schema"))
				},
				func(w io.Writer, _ int, reqBody reqBody, _ []byte) error {
					fmt.Fprintf(w, "group %s is created\n", reqBody.name)
					return nil
				}, enableTLS, insecure, cert)
		},
	}

	updateCmd := &cobra.Command{
		Use:     "update -f [file|dir|-]",
		Version: version.Build(),
		Short:   "Update groups from files",
		RunE: func(cmd *cobra.Command, _ []string) (err error) {
			return rest(cmd.OutOrStdout(), func() ([]reqBody, error) { return parseNameFromYAML(cmd.InOrStdin()) },
				func(request request) (*resty.Response, error) {
					g := new(commonv1.Group)
					err := protojson.Unmarshal(request.data, g)
					if err != nil {
						return nil, err
					}
					cr := &databasev1.GroupRegistryServiceUpdateRequest{
						Group: g,
					}
					b, err := protojson.Marshal(cr)
					if err != nil {
						return nil, err
					}
					return request.req.SetBody(b).SetPathParam("group", request.name).Put(getPath("/api/v1/group/schema/{group}"))
				},
				func(w io.Writer, _ int, reqBody reqBody, _ []byte) error {
					fmt.Fprintf(w, "group %s is updated\n", reqBody.name)
					return nil
				}, enableTLS, insecure, cert)
		},
	}
	bindFileFlag(createCmd, updateCmd)

	getCmd := &cobra.Command{
		Use:     "get [-g group]",
		Version: version.Build(),
		Short:   "Get a group",
		RunE: func(cmd *cobra.Command, _ []string) (err error) {
			return rest(cmd.OutOrStdout(), parseFromFlags, func(request request) (*resty.Response, error) {
				return request.req.SetPathParam("group", request.group).Get(getPath("/api/v1/group/schema/{group}"))
			}, yamlPrinter, enableTLS, insecure, cert)
		},
	}

	var force, dryRun, dataOnly bool
	deleteCmd := &cobra.Command{
		Use:     "delete [-g group] [--force] [--dry-run] [--data-only]",
		Version: version.Build(),
		Short:   "Delete a group",
		RunE: func(cmd *cobra.Command, _ []string) (err error) {
			return rest(cmd.OutOrStdout(), parseFromFlags, func(request request) (*resty.Response, error) {
				return request.req.
					SetPathParam("group", request.group).
					SetQueryParam("force", fmt.Sprintf("%v", force)).
					SetQueryParam("dry_run", fmt.Sprintf("%v", dryRun)).
					SetQueryParam("data_only", fmt.Sprintf("%v", dataOnly)).
					Delete(getPath("/api/v1/group/schema/{group}"))
			},
				func(w io.Writer, _ int, reqBody reqBody, respBody []byte) error {
					if dryRun {
						fmt.Fprintf(w, "group %s dry-run deletion result:\n", reqBody.group)
						return yamlPrinter(w, 0, reqBody, respBody)
					}
					fmt.Fprintf(w, "group %s is deleted\n", reqBody.group)
					return nil
				}, enableTLS, insecure, cert)
		},
	}
	deleteCmd.Flags().BoolVarP(&force, "force", "", false, "Force delete the group even if it contains data")
	deleteCmd.Flags().BoolVarP(&dryRun, "dry-run", "", false, "Preview what would be deleted without making changes")
	deleteCmd.Flags().BoolVarP(&dataOnly, "data-only", "", false, "Delete only data files without removing metadata")

	listCmd := &cobra.Command{
		Use:     "list",
		Version: version.Build(),
		Short:   "List all groups",
		RunE: func(cmd *cobra.Command, _ []string) (err error) {
			return rest(cmd.OutOrStdout(), nil, func(request request) (*resty.Response, error) {
				return request.req.Get(getPath("/api/v1/group/schema/lists"))
			}, yamlPrinter, enableTLS, insecure, cert)
		},
	}

	bindTLSRelatedFlag(createCmd, updateCmd, listCmd, getCmd, deleteCmd)
	groupCmd.AddCommand(createCmd, updateCmd, listCmd, getCmd, deleteCmd)
	return groupCmd
}
