// Copyright F3 Authors
// SPDX-License-Identifier: MIT

package forgejo

import (
	"context"
	"fmt"
	"strings"

	"code.forgejo.org/f3/gof3/v3/f3"
	helper_forge "code.forgejo.org/f3/gof3/v3/forges/helpers/forge"
	"code.forgejo.org/f3/gof3/v3/id"
	f3_tree "code.forgejo.org/f3/gof3/v3/tree/f3"
	"code.forgejo.org/f3/gof3/v3/tree/generic"
	"code.forgejo.org/f3/gof3/v3/util"

	forgejo_sdk "code.forgejo.org/f3/gof3/v3/forges/forgejo/sdk"
)

type user struct {
	common
	forgejoUser *forgejo_sdk.User
	Password    string
}

var _ f3_tree.ForgeDriverInterface = &user{}

const fakeEmailSuffix = ".fakeemail"

func fromFakeEmail(mail string) string {
	return strings.TrimSuffix(mail, fakeEmailSuffix)
}

func toFakeEmail(mail string) string {
	if !strings.HasSuffix(mail, fakeEmailSuffix) {
		return mail + fakeEmailSuffix
	}
	return mail
}

func newUser() generic.NodeDriverInterface {
	return &user{}
}

const (
	GhostUserID     = int64(-1)
	GhostUserName   = "Ghost"
	ActionsUserID   = int64(-2)
	ActionsUserName = "forgejo-actions"
)

func getGhostUser() *forgejo_sdk.User {
	return &forgejo_sdk.User{
		ID:       GhostUserID,
		UserName: GhostUserName,
		FullName: "Ghost",
		Email:    "ghost@forgejo.org",
	}
}

func getActionsUser() *forgejo_sdk.User {
	return &forgejo_sdk.User{
		ID:       ActionsUserID,
		UserName: ActionsUserName,
		FullName: "Forgejo Actions",
		Email:    "noreply@forgejo.org",
	}
}

func getSystemUserByID(id int64) *forgejo_sdk.User {
	switch id {
	case GhostUserID:
		return getGhostUser()
	case ActionsUserID:
		return getActionsUser()
	default:
		return nil
	}
}

func (o *user) getSystemUserByID(ctx context.Context, id int64) *forgejo_sdk.User {
	if id < 0 {
		return getSystemUserByID(id)
	}
	return nil
}

func (o *user) SetNative(user any) {
	o.forgejoUser = user.(*forgejo_sdk.User)
}

func (o *user) GetNativeID() string {
	return util.ToString(o.forgejoUser.ID)
}

func (o *user) NewFormat() f3.Interface {
	node := o.GetNode()
	return node.GetTree().(f3_tree.TreeInterface).NewFormat(node.GetKind())
}

func (o *user) ToFormat() f3.Interface {
	if o.forgejoUser == nil {
		return o.NewFormat()
	}
	return &f3.User{
		Common:   f3.NewCommon(util.ToString(o.forgejoUser.ID)),
		UserName: o.forgejoUser.UserName,
		Name:     o.forgejoUser.FullName,
		Email:    o.forgejoUser.Email,
		IsAdmin:  o.forgejoUser.IsAdmin,
		Password: o.Password,
	}
}

func (o *user) FromFormat(content f3.Interface) {
	user := content.(*f3.User)
	o.forgejoUser = &forgejo_sdk.User{
		ID:       util.ParseInt(user.GetID()),
		UserName: user.UserName,
		FullName: user.Name,
		Email:    user.Email,
		IsAdmin:  user.IsAdmin,
	}
	o.Password = user.Password
}

func (o *user) Get(ctx context.Context) bool {
	node := o.GetNode()
	o.Trace("%s", node.GetID())
	if user := o.getSystemUserByID(ctx, node.GetID().Int64()); user != nil {
		o.forgejoUser = user
		options := o.GetTreeDriver().(*treeDriver).options
		o.forgejoUser.UserName += helper_forge.URLToUser(options.GetURL())
		return true
	}
	var user *forgejo_sdk.User
	var err error
	if node.GetID() != id.NilID {
		user, _, err = o.getClient().GetUserByID(node.GetID().Int64())
	} else {
		panic("GetID() == 0")
	}
	if err != nil {
		if strings.Contains(err.Error(), "user not found") {
			return false
		}
		panic(fmt.Errorf("user %v %w", o, err))
	}
	user.Email = fromFakeEmail(user.Email)
	o.forgejoUser = user
	return true
}

func (o *user) Patch(context.Context) {
	node := o.GetNode()
	o.Trace("%s", node.GetID())

	_, err := o.getClient().AdminEditUser(o.forgejoUser.UserName, forgejo_sdk.EditUserOption{
		LoginName: o.forgejoUser.UserName, // this is redundant but required by Gitea
		FullName:  &o.forgejoUser.FullName,
		Admin:     &o.forgejoUser.IsAdmin,
	})
	if err != nil {
		panic(fmt.Errorf("AdminEditUser %v %w", o, err))
	}
}

func (o *user) Put(ctx context.Context) id.NodeID {
	mustChangePassword := false

	if o.Password == "" {
		o.Password = util.RandSeq(30)
	}

	email := o.forgejoUser.Email
	u, _, err := o.getClient().AdminCreateUser(forgejo_sdk.CreateUserOption{
		Username:           o.forgejoUser.UserName,
		FullName:           o.forgejoUser.FullName,
		Email:              toFakeEmail(email),
		Password:           o.Password,
		MustChangePassword: &mustChangePassword,
	})
	if err != nil {
		panic(fmt.Errorf("%v: %w", o.forgejoUser, err))
	}
	o.forgejoUser = u
	o.forgejoUser.Email = email // Gitea will lowercase the email address
	o.Trace("%s %d", o.forgejoUser.UserName, o.forgejoUser.ID)
	if o.forgejoUser.IsAdmin {
		o.Patch(ctx)
	}
	return id.NewNodeID(u.ID)
}

func (o *user) Delete(ctx context.Context) {
	if user := getSystemUserByID(o.forgejoUser.ID); user != nil {
		return
	}

	o.Trace("%s %d", o.forgejoUser.UserName, o.forgejoUser.ID)

	if o.isSelf(o.forgejoUser.ID) {
		o.Debug("silently ignore a request to delete self")
		return
	}

	_, err := o.getClient().AdminDeleteUser(o.forgejoUser.UserName)
	if err != nil {
		panic(fmt.Sprintf("%v: %v", o.forgejoUser.UserName, err))
	}
}
