/*
 * Copyright 2026 Oleg Borodin <onborodin@gmail.com>
 *
 * This work is published and licensed under a Creative Commons
 * Attribution-NonCommercial-NoDerivatives 4.0 International License.
 *
 * Distribution of this work is permitted, but commercial use and
 * modifications are strictly prohibited.
 */
package main

import (
	"context"
	"regexp"
	"strings"
	"time"

	"github.com/spf13/cobra"
	"github.com/spf13/viper"

	"mstore/pkg/client"
	"mstore/pkg/descr"
	"mstore/pkg/terms"
)

const (
	uuidRegex       = `^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$`
	defaultHostname = "localhost:1025"
)

type AccountUtil struct {
	createAccountParams CreateAccountParams
	updateAccountParams UpdateAccountParams
	getAccountParams    GetAccountParams
	deleteAccountParams DeleteAccountParams
	listAccountsParams  ListAccountsParams
	commonAccountParams CommonAccountParams
}

type CommonAccountParams struct {
	Username      string
	Password      string
	Hostname      string
	Timeout       uint64
	SkipTLSVerify bool
}

func (util *AccountUtil) CreateAccountCmds() *cobra.Command {
	var subCmd = &cobra.Command{
		Use:     "accounts",
		Short:   "Account operations",
		Aliases: []string{"account"},
	}
	const defaultTimeout uint64 = 10

	subCmd.PersistentFlags().StringVarP(&util.commonAccountParams.Username, "user", "U", util.commonAccountParams.Username, "Username")
	subCmd.PersistentFlags().StringVarP(&util.commonAccountParams.Password, "pass", "P", util.commonAccountParams.Password, "Password")
	subCmd.PersistentFlags().StringVarP(&util.commonAccountParams.Hostname, "host", "X", defaultHostname, "Hostname")
	subCmd.PersistentFlags().Uint64VarP(&util.commonAccountParams.Timeout, "timeout", "T", defaultTimeout, "Operation timeout")
	subCmd.PersistentFlags().BoolVarP(&util.commonAccountParams.SkipTLSVerify, "skipVerify", "S", true, "Skip server certificate verify")
	subCmd.MarkFlagsRequiredTogether("user", "pass")

	vi := viper.New()
	vi.SetEnvPrefix("mstore")
	vi.BindEnv("user")
	vi.BindEnv("pass")
	util.commonAccountParams.Username = vi.GetString("user")
	util.commonAccountParams.Password = vi.GetString("pass")

	// CreateAccount
	var createAccountCmd = &cobra.Command{
		Use:   "create [user:pass@]hostname[:port] username password",
		Short: "Create user account",
		Args:  cobra.ExactArgs(3),
		Run:   util.CreateAccount,
	}
	subCmd.AddCommand(createAccountCmd)

	// GetAccount
	var getAccountCmd = &cobra.Command{
		Use:   "get [user:pass@]hostname[:port] accountId|username",
		Short: "Get account info",
		Args:  cobra.ExactArgs(2),
		Run:   util.GetAccount,
	}
	subCmd.AddCommand(getAccountCmd)

	// UpdateAccount
	var updateAccountCmd = &cobra.Command{
		Use:   "update [user:pass@]hostname[:port] username|accounId",
		Short: "Update account parameters",
		Args:  cobra.ExactArgs(2),
		Run:   util.UpdateAccount,
	}
	updateAccountCmd.Flags().StringVarP(&util.updateAccountParams.NewUsername, "newname", "u", "", "New username")
	updateAccountCmd.Flags().StringVarP(&util.updateAccountParams.NewPassword, "newpass", "p", "", "New password")
	updateAccountCmd.MarkFlagsOneRequired("newname", "newpass")
	subCmd.AddCommand(updateAccountCmd)

	// DeleteAccount
	var deleteAccountCmd = &cobra.Command{
		Use:   "delete [user:pass@]hostname[:port] username|accountId",
		Short: "Delete account",
		Args:  cobra.ExactArgs(2),
		Run:   util.DeleteAccount,
	}
	subCmd.AddCommand(deleteAccountCmd)

	// ListAccount
	var listAccountsCmd = &cobra.Command{
		Use:   "list [user:pass@]hostname[:port]",
		Short: "list accounts",
		Args:  cobra.ExactArgs(1),
		Run:   util.ListAccounts,
	}
	listAccountsCmd.Flags().BoolVarP(&util.listAccountsParams.Detail, "detail", "d", false, "Show detail information")
	listAccountsCmd.Flags().StringVarP(&util.listAccountsParams.Regex, "regex", "r", "", "Output regexp for usernames")
	subCmd.AddCommand(listAccountsCmd)

	return subCmd
}

// CreateAccount
type CreateAccountParams struct {
	NewUsername string
	NewPassword string
}
type CreateAccountResult struct {
	AccountID string            `yaml:"accountId"`
	Grants    map[string]string `yaml:"grantsIds,omitempty"`
}

func (util *AccountUtil) CreateAccount(cmd *cobra.Command, args []string) {
	util.commonAccountParams.Hostname = args[0]
	util.createAccountParams.NewUsername = args[1]
	util.createAccountParams.NewPassword = args[2]
	res, err := util.createAccount(&util.commonAccountParams, &util.createAccountParams)
	printResponse(res, err)
}

func (util *AccountUtil) createAccount(common *CommonAccountParams, params *CreateAccountParams) (*CreateAccountResult, error) {
	var err error
	res := &CreateAccountResult{
		Grants: make(map[string]string, 0),
	}
	hostname, err := packUserinfo(common.Hostname, common.Username, common.Password)
	if err != nil {
		return res, err
	}
	timeout := time.Duration(common.Timeout) * time.Second
	ctx, _ := context.WithTimeout(context.Background(), timeout)
	accountID, err := client.NewClient(common.SkipTLSVerify).CreateAccount(ctx, hostname, params.NewUsername, params.NewPassword)
	if err != nil {
		return res, err
	}

	fullRights := []string{
		terms.RightWriteAccounts,
		terms.RightReadAccounts,
		terms.RightWriteFiles,
		terms.RightReadFiles,
		terms.RightWriteImages,
		terms.RightReadImages,
	}
	for _, right := range fullRights {
		id, err := client.NewClient(common.SkipTLSVerify).CreateGrantByAccountID(ctx, hostname, accountID, right, ".*")
		if err != nil {
			return res, err
		}
		res.Grants[id] = right
	}
	res.AccountID = accountID
	return res, err
}

// UpdateAccount
type UpdateAccountParams struct {
	Timeout     uint64
	AccountID   string
	NewUsername string
	NewPassword string
}
type UpdateAccountResult struct {
	File *descr.File `yaml:"file,omitempty"`
}

func (util *AccountUtil) UpdateAccount(cmd *cobra.Command, args []string) {
	util.commonAccountParams.Hostname = args[0]
	util.updateAccountParams.AccountID = args[1]
	res, err := util.updateAccount(&util.commonAccountParams, &util.updateAccountParams)
	printResponse(res, err)
}

func (util *AccountUtil) updateAccount(common *CommonAccountParams, params *UpdateAccountParams) (*UpdateAccountResult, error) {
	var err error
	res := &UpdateAccountResult{}
	hostname, err := packUserinfo(common.Hostname, common.Username, common.Password)
	if err != nil {
		return res, err
	}
	timeout := time.Duration(common.Timeout) * time.Second
	ctx, _ := context.WithTimeout(context.Background(), timeout)
	re := regexp.MustCompile(uuidRegex)
	id := strings.ToLower(params.AccountID)
	if re.MatchString(id) {
		err = client.NewClient(common.SkipTLSVerify).UpdateAccountByID(ctx, hostname, id, params.NewUsername, params.NewPassword)
	} else {
		err = client.NewClient(common.SkipTLSVerify).UpdateAccountByName(ctx, hostname, params.AccountID, params.NewUsername, params.NewPassword)
	}
	if err != nil {
		return res, err
	}
	return res, err
}

// Get file
type GetAccountParams struct {
	Timeout   uint64
	AccountID string
}

func (util *AccountUtil) GetAccount(cmd *cobra.Command, args []string) {
	util.commonAccountParams.Hostname = args[0]
	util.getAccountParams.AccountID = args[1]
	res, err := util.getAccount(&util.commonAccountParams, &util.getAccountParams)
	printResponse(res, err)
}

type GetAccountResult struct {
	Account *descr.AccountShort `yaml:"account,omitempty"`
}

func (util *AccountUtil) getAccount(common *CommonAccountParams, params *GetAccountParams) (*GetAccountResult, error) {
	var err error
	res := &GetAccountResult{}
	hostname, err := packUserinfo(common.Hostname, common.Username, common.Password)
	if err != nil {
		return res, err
	}
	timeout := time.Duration(common.Timeout) * time.Second
	ctx, _ := context.WithTimeout(context.Background(), timeout)
	opRes := &descr.AccountShort{}

	re := regexp.MustCompile(uuidRegex)
	id := strings.ToLower(params.AccountID)
	if re.MatchString(id) {
		opRes, err = client.NewClient(common.SkipTLSVerify).GetAccountByID(ctx, hostname, id)
	} else {
		opRes, err = client.NewClient(common.SkipTLSVerify).GetAccountByName(ctx, hostname, params.AccountID)
	}
	if err != nil {
		return res, err
	}
	res.Account = opRes
	return res, err
}

// DeleteAccount
type DeleteAccountParams struct {
	AccountID string
	Timeout   uint64
}

type DeleteAccountResult struct{}

func (util *AccountUtil) DeleteAccount(cmd *cobra.Command, args []string) {
	util.commonAccountParams.Hostname = args[0]
	util.deleteAccountParams.AccountID = args[1]
	res, err := util.deleteAccount(&util.commonAccountParams, &util.deleteAccountParams)
	printResponse(res, err)
}
func (util *AccountUtil) deleteAccount(common *CommonAccountParams, params *DeleteAccountParams) (*DeleteAccountResult, error) {
	var err error
	res := &DeleteAccountResult{}
	hostname, err := packUserinfo(common.Hostname, common.Username, common.Password)
	if err != nil {
		return res, err
	}
	timeout := time.Duration(common.Timeout) * time.Second
	ctx, _ := context.WithTimeout(context.Background(), timeout)
	re := regexp.MustCompile(uuidRegex)
	id := strings.ToLower(params.AccountID)
	if re.MatchString(id) {
		err = client.NewClient(common.SkipTLSVerify).DeleteAccountByID(ctx, hostname, id)
	} else {
		err = client.NewClient(common.SkipTLSVerify).DeleteAccountByName(ctx, hostname, params.AccountID)
	}
	if err != nil {
		return res, err
	}
	return res, err
}

// ListAccounts
type ListAccountsParams struct {
	Timeout uint64
	Detail  bool
	Regex   string
}

type Userinfo struct {
	Username  string            `yaml:"username,omitempty"`
	AccountID string            `yaml:"accountId,omitempty"`
	Rights    map[string]string `yaml:"rights,omitempty"`
}

type ListAccountsResult struct {
	Accounts []descr.AccountShort `yaml:"accounts,omitempty"`
	Users    []Userinfo           `yaml:"users,omitempty"`
}

func (util *AccountUtil) ListAccounts(cmd *cobra.Command, args []string) {
	util.commonAccountParams.Hostname = args[0]
	res, err := util.listAccounts(&util.commonAccountParams, &util.listAccountsParams)
	printResponse(res, err)
}
func (util *AccountUtil) listAccounts(common *CommonAccountParams, params *ListAccountsParams) (*ListAccountsResult, error) {
	var err error
	res := &ListAccountsResult{}
	hostname, err := packUserinfo(common.Hostname, common.Username, common.Password)
	if err != nil {
		return res, err
	}
	outRe, err := regexp.Compile(params.Regex)
	if err != nil {
		return res, err
	}

	timeout := time.Duration(common.Timeout) * time.Second
	ctx, _ := context.WithTimeout(context.Background(), timeout)
	accounts, err := client.NewClient(common.SkipTLSVerify).ListAccounts(ctx, hostname)
	if err != nil {
		return res, err
	}
	outAccounts := make([]descr.AccountShort, 0)
	if params.Regex != "" {
		for _, item := range accounts {
			if outRe.MatchString(item.Username) {
				outAccounts = append(outAccounts, item)
			}
		}
	} else {
		outAccounts = accounts
	}
	if params.Detail {
		res.Accounts = outAccounts
	} else {
		res.Users = make([]Userinfo, 0)
		for _, account := range outAccounts {
			userinfo := Userinfo{
				Username:  account.Username,
				AccountID: account.ID,
				Rights:    make(map[string]string, 0),
			}
			for _, grant := range account.Grants {
				userinfo.Rights[grant.ID] = grant.Right
			}
			res.Users = append(res.Users, userinfo)
		}
	}
	return res, err
}
