package advice

import (
	"encoding/json"
	"fmt"
	"net/url"
	"os"
	"strconv"

	"github.com/spf13/cobra"

	"github.com/tidbcloud/aisupport/apps/cli/ticket-manager/common"
	wsapi "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/api/ticket-manager/workspace"
	wsent "github.com/tidbcloud/aisupport/packages/sdk-go/contracts/entity/ticket-manager/workspace"
	sliceutil "github.com/tidbcloud/aisupport/packages/sdk-go/infra/utils/container/slice"
)

// NewCommand builds workspace advice subcommands.
func NewCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "advice",
		Short: "Manage workspace advices",
	}

	cmd.AddCommand(newListCommand())
	cmd.AddCommand(newGetCommand())
	cmd.AddCommand(newCreateCommand())
	cmd.AddCommand(newUpdateCommand())
	cmd.AddCommand(newApplyCommand())
	cmd.AddCommand(newIgnoreCommand())
	cmd.AddCommand(newDeleteCommand())

	return cmd
}

func newListCommand() *cobra.Command {
	var workspaceID uint64
	var adviceTypes []string
	var page uint32 = 1
	var pageSize uint32 = 100

	cmd := &cobra.Command{
		Use:   "list",
		Short: "List workspace advices",
		RunE: func(cmd *cobra.Command, args []string) error {
			query := url.Values{}
			if workspaceID != 0 {
				query.Set("workspace_id", strconv.FormatUint(workspaceID, 10))
			}
			query.Set("page", strconv.FormatUint(uint64(page), 10))
			query.Set("page_size", strconv.FormatUint(uint64(pageSize), 10))
			for _, adviceType := range adviceTypes {
				query.Add("type", adviceType)
			}

			var result wsapi.ListAdvicesResponse
			_, err := common.Client.R().
				SetContext(cmd.Context()).
				SetResult(&result).
				SetQueryParamsFromValues(query).
				Get("/workspace/advices")
			if err != nil {
				return fmt.Errorf("list workspace advices: %w", err)
			}

			return common.Printer.PrintList(sliceutil.TransformSlice(result.Advices, func(value *wsent.Advice) any {
				return value
			}))
		},
	}

	cmd.Flags().Uint64Var(&workspaceID, "workspace-id", 0, "Filter by workspace ID")
	cmd.Flags().StringArrayVar(&adviceTypes, "type", nil, "Filter by advice type")
	cmd.Flags().Uint32Var(&page, "page", page, "Page number")
	cmd.Flags().Uint32Var(&pageSize, "page-size", pageSize, "Page size")

	return cmd
}

func newGetCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "get <advice-id>",
		Short: "Get a workspace advice",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			adviceID, err := parseUintArg("advice id", args[0])
			if err != nil {
				return err
			}

			var result wsapi.GetAdviceResponse
			_, err = common.Client.R().
				SetContext(cmd.Context()).
				SetPathParam("id", strconv.FormatUint(adviceID, 10)).
				SetResult(&result).
				Get("/workspace/advices/{id}")
			if err != nil {
				return fmt.Errorf("get workspace advice %d: %w", adviceID, err)
			}

			return common.Printer.PrintObject(result.Advice)
		},
	}

	return cmd
}

func newCreateCommand() *cobra.Command {
	var req wsapi.CreateAdviceRequest
	var workspaceID uint64
	var data string
	var dataFile string
	var adviceType string

	cmd := &cobra.Command{
		Use:   "create",
		Short: "Create a workspace advice",
		PreRunE: func(cmd *cobra.Command, args []string) error {
			adviceData, err := readAdviceData(data, dataFile)
			if err != nil {
				return err
			}
			if adviceData == nil {
				return fmt.Errorf("--data or --data-file is required")
			}

			req.WorkspaceID = workspaceID
			req.Data = *adviceData
			req.Type = wsent.AdivceType(adviceType)
			return nil
		},
		RunE: func(cmd *cobra.Command, args []string) error {
			var result wsapi.CreateAdviceResponse
			_, err := common.Client.R().
				SetContext(cmd.Context()).
				SetBody(&req).
				SetResult(&result).
				Post("/workspace/advices")
			if err != nil {
				return fmt.Errorf("create workspace advice for workspace %d: %w", req.WorkspaceID, err)
			}

			return common.Printer.PrintObject(result.Advice)
		},
	}

	cmd.Flags().Uint64Var(&workspaceID, "workspace-id", 0, "Workspace ID")
	cmd.Flags().StringVar(&req.Description, "description", "", "Advice description")
	cmd.Flags().StringVar(&adviceType, "type", "", "Advice type")
	cmd.Flags().StringVar(&data, "data", "", "Advice data JSON")
	cmd.Flags().StringVar(&dataFile, "data-file", "", "Path to advice data JSON file")
	cmd.Flags().StringVar(&req.ReplyMessageID, "reply-message-id", "", "Lark message ID to reply to")
	_ = cmd.MarkFlagRequired("workspace-id")
	_ = cmd.MarkFlagRequired("description")
	_ = cmd.MarkFlagRequired("type")

	return cmd
}

func newUpdateCommand() *cobra.Command {
	var req wsapi.UpdateAdviceRequest
	var data string
	var dataFile string

	cmd := &cobra.Command{
		Use:   "update <advice-id>",
		Short: "Update a workspace advice",
		Args:  cobra.ExactArgs(1),
		PreRunE: func(cmd *cobra.Command, args []string) error {
			req = wsapi.UpdateAdviceRequest{}
			if cmd.Flags().Changed("description") {
				value, err := cmd.Flags().GetString("description")
				if err != nil {
					return err
				}
				req.Description = &value
			}

			adviceData, err := readAdviceData(data, dataFile)
			if err != nil {
				return err
			}
			if adviceData != nil {
				req.Data = adviceData
			}
			if req.Description == nil && req.Data == nil {
				return fmt.Errorf("at least one update field is required")
			}

			return nil
		},
		RunE: func(cmd *cobra.Command, args []string) error {
			adviceID, err := parseUintArg("advice id", args[0])
			if err != nil {
				return err
			}

			var result wsapi.UpdateAdviceResponse
			_, err = common.Client.R().
				SetContext(cmd.Context()).
				SetPathParam("id", strconv.FormatUint(adviceID, 10)).
				SetBody(&req).
				SetResult(&result).
				Put("/workspace/advices/{id}")
			if err != nil {
				return fmt.Errorf("update workspace advice %d: %w", adviceID, err)
			}

			return common.Printer.PrintObject(result.Advice)
		},
	}

	cmd.Flags().String("description", "", "Advice description")
	cmd.Flags().StringVar(&data, "data", "", "Advice data JSON")
	cmd.Flags().StringVar(&dataFile, "data-file", "", "Path to advice data JSON file")

	return cmd
}

func newApplyCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "apply <advice-id>",
		Short: "Apply a workspace advice",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			adviceID, err := parseUintArg("advice id", args[0])
			if err != nil {
				return err
			}

			var result wsapi.ApplyAdviceResponse
			_, err = common.Client.R().
				SetContext(cmd.Context()).
				SetPathParam("id", strconv.FormatUint(adviceID, 10)).
				SetResult(&result).
				Post("/workspace/advices/{id}/apply")
			if err != nil {
				return fmt.Errorf("apply workspace advice %d: %w", adviceID, err)
			}

			return common.Printer.PrintObject(result.Advice)
		},
	}

	return cmd
}

func newIgnoreCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "ignore <advice-id>",
		Short: "Ignore a workspace advice",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			adviceID, err := parseUintArg("advice id", args[0])
			if err != nil {
				return err
			}

			var result wsapi.IgnoreAdviceResponse
			_, err = common.Client.R().
				SetContext(cmd.Context()).
				SetPathParam("id", strconv.FormatUint(adviceID, 10)).
				SetResult(&result).
				Post("/workspace/advices/{id}/ignore")
			if err != nil {
				return fmt.Errorf("ignore workspace advice %d: %w", adviceID, err)
			}

			return common.Printer.PrintObject(result.Advice)
		},
	}

	return cmd
}

func newDeleteCommand() *cobra.Command {
	cmd := &cobra.Command{
		Use:   "delete <advice-id>",
		Short: "Delete a workspace advice",
		Args:  cobra.ExactArgs(1),
		RunE: func(cmd *cobra.Command, args []string) error {
			adviceID, err := parseUintArg("advice id", args[0])
			if err != nil {
				return err
			}

			var result wsapi.DeleteAdviceResponse
			_, err = common.Client.R().
				SetContext(cmd.Context()).
				SetPathParam("id", strconv.FormatUint(adviceID, 10)).
				SetResult(&result).
				Delete("/workspace/advices/{id}")
			if err != nil {
				return fmt.Errorf("delete workspace advice %d: %w", adviceID, err)
			}

			return common.Printer.PrintObject(result.Advice)
		},
	}

	return cmd
}

func readAdviceData(raw string, path string) (*wsent.AdviceData, error) {
	if raw != "" && path != "" {
		return nil, fmt.Errorf("--data and --data-file cannot be used together")
	}
	if path != "" {
		data, err := os.ReadFile(path)
		if err != nil {
			return nil, fmt.Errorf("read data file %s: %w", path, err)
		}
		raw = string(data)
	}
	if raw == "" {
		return nil, nil
	}

	var data wsent.AdviceData
	if err := json.Unmarshal([]byte(raw), &data); err != nil {
		return nil, fmt.Errorf("invalid JSON data: %w", err)
	}
	return &data, nil
}

func parseUintArg(name string, value string) (uint64, error) {
	id, err := strconv.ParseUint(value, 10, 64)
	if err != nil {
		return 0, fmt.Errorf("invalid %s %q: %w", name, value, err)
	}
	return id, nil
}
