// get_item.go is a source file designed to retrieve an m365 object from an
// existing M365 account. Data displayed is representative of the current
// serialization abstraction versioning used by Microsoft Graph and stored by Corso.

package exchange

import (
	"context"
	"fmt"
	"os"

	"github.com/alcionai/clues"
	"github.com/microsoft/kiota-abstractions-go/serialization"
	kw "github.com/microsoft/kiota-serialization-json-go"
	"github.com/spf13/cobra"

	"github.com/alcionai/corso/src/cli/utils"
	"github.com/alcionai/corso/src/internal/common/ptr"
	"github.com/alcionai/corso/src/internal/common/str"
	"github.com/alcionai/corso/src/pkg/account"
	"github.com/alcionai/corso/src/pkg/backup/details"
	"github.com/alcionai/corso/src/pkg/credentials"
	"github.com/alcionai/corso/src/pkg/fault"
	"github.com/alcionai/corso/src/pkg/path"
	"github.com/alcionai/corso/src/pkg/services/m365/api"
)

// Required inputs from user for command execution
var (
	user, tenant, m365ID, category string
)

func AddCommands(parent *cobra.Command) {
	exCmd := &cobra.Command{
		Use:   "exchange",
		Short: "Get an M365ID item JSON",
		RunE:  handleExchangeCmd,
	}

	fs := exCmd.PersistentFlags()
	fs.StringVar(&m365ID, "id", "", "m365 identifier for object")
	fs.StringVar(&category, "category", "", "type of M365 data (contacts, email, events)")
	fs.StringVar(&user, "user", "", "m365 user id of M365 user")
	fs.StringVar(&tenant, "tenant", "", "m365 identifier for the tenant")

	cobra.CheckErr(exCmd.MarkPersistentFlagRequired("user"))
	cobra.CheckErr(exCmd.MarkPersistentFlagRequired("id"))
	cobra.CheckErr(exCmd.MarkPersistentFlagRequired("category"))

	parent.AddCommand(exCmd)
}

func handleExchangeCmd(cmd *cobra.Command, args []string) error {
	if utils.HasNoFlagsAndShownHelp(cmd) {
		return nil
	}

	tid := str.First(tenant, os.Getenv(account.AzureTenantID))

	ctx := clues.Add(
		cmd.Context(),
		"item_id", m365ID,
		"resource_owner", user,
		"tenant", tid)

	creds := account.M365Config{
		M365:          credentials.GetM365(),
		AzureTenantID: tid,
	}

	err := runDisplayM365JSON(ctx, creds, user, m365ID, fault.New(true))
	if err != nil {
		cmd.SilenceUsage = true
		cmd.SilenceErrors = true

		return clues.Wrap(err, "getting item")
	}

	return nil
}

func runDisplayM365JSON(
	ctx context.Context,
	creds account.M365Config,
	user, itemID string,
	errs *fault.Bus,
) error {
	var (
		bs  []byte
		err error
		cat = path.ToCategoryType(category)
		sw  = kw.NewJsonSerializationWriter()
	)

	ac, err := api.NewClient(creds)
	if err != nil {
		return err
	}

	switch cat {
	case path.EmailCategory:
		bs, err = getItem(ctx, ac.Mail(), user, itemID, true, errs)
	case path.EventsCategory:
		bs, err = getItem(ctx, ac.Events(), user, itemID, true, errs)
	case path.ContactsCategory:
		bs, err = getItem(ctx, ac.Contacts(), user, itemID, true, errs)
	default:
		return fmt.Errorf("unable to process category: %s", cat)
	}

	if err != nil {
		return err
	}

	err = sw.WriteStringValue("", ptr.To(string(bs)))
	if err != nil {
		return clues.Wrap(err, "Error writing string value: "+itemID)
	}

	array, err := sw.GetSerializedContent()
	if err != nil {
		return clues.Wrap(err, "Error serializing item: "+itemID)
	}

	fmt.Println(string(array))

	return nil
}

type itemer interface {
	GetItem(
		ctx context.Context,
		user, itemID string,
		immutableID bool,
		errs *fault.Bus,
	) (serialization.Parsable, *details.ExchangeInfo, error)
	Serialize(
		ctx context.Context,
		item serialization.Parsable,
		user, itemID string,
	) ([]byte, error)
}

func getItem(
	ctx context.Context,
	itm itemer,
	user, itemID string,
	immutableIDs bool,
	errs *fault.Bus,
) ([]byte, error) {
	sp, _, err := itm.GetItem(ctx, user, itemID, immutableIDs, errs)
	if err != nil {
		return nil, clues.Wrap(err, "getting item")
	}

	return itm.Serialize(ctx, sp, user, itemID)
}
