// Copyright Earl Warren <contact@earl-warren.org>
// Copyright Loïc Dachary <loic@dachary.org>
// SPDX-License-Identifier: MIT

package forgejo

import (
	"context"
	"fmt"
	"strings"

	"code.forgejo.org/f3/gof3/v3/f3"
	"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"
	"github.com/hashicorp/go-version"
)

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"
)

var SystemUserMinVersion = ForgejoVersion600

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 getSystemUserByName(name string) *forgejo_sdk.User {
	switch name {
	case GhostUserName:
		return getGhostUser()
	case ActionsUserName:
		return getActionsUser()
	default:
		return nil
	}
}

// Hardcode system users for Forgejo versions that do not support it
func (o *user) getSystemUserByID(ctx context.Context, minVersion *version.Version, id int64) *forgejo_sdk.User {
	if id < 0 {
		if o.getVersion().LessThan(minVersion) {
			return getSystemUserByID(id)
		}
	}
	return nil
}

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

func (o *user) GetNativeID() string {
	return fmt.Sprintf("%d", 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(fmt.Sprintf("%d", 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, SystemUserMinVersion, node.GetID().Int64()); user != nil {
		o.forgejoUser = user
		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())
}

func (o *user) Put(context.Context) id.NodeID {
	if user := getSystemUserByName(o.forgejoUser.UserName); user != nil {
		o.forgejoUser.UserName += "PLACEHOLDER"
		o.forgejoUser.Email = o.forgejoUser.UserName + "@example.com"
	}

	mustChangePassword := false

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

	u, _, err := o.getClient().AdminCreateUser(forgejo_sdk.CreateUserOption{
		Username:           o.forgejoUser.UserName,
		FullName:           o.forgejoUser.FullName,
		Email:              toFakeEmail(o.forgejoUser.Email),
		Password:           o.Password,
		MustChangePassword: &mustChangePassword,
	})
	if err != nil {
		panic(fmt.Errorf("%v: %w", o.forgejoUser, err))
	}
	o.forgejoUser = u
	o.forgejoUser.Email = fromFakeEmail(o.forgejoUser.Email)
	o.Trace("%s %d", o.forgejoUser.UserName, o.forgejoUser.ID)
	return id.NewNodeID(u.ID)
}

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

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