package user

import (
	"fmt"
	"time"

	"github.com/cloudreve/Cloudreve/v4/ent"
	"github.com/cloudreve/Cloudreve/v4/ent/user"
	"github.com/cloudreve/Cloudreve/v4/inventory/types"
	"github.com/cloudreve/Cloudreve/v4/pkg/auth"
	"github.com/cloudreve/Cloudreve/v4/pkg/boolset"
	"github.com/cloudreve/Cloudreve/v4/pkg/hashid"
	"github.com/go-webauthn/webauthn/protocol"
	"github.com/go-webauthn/webauthn/webauthn"
	"github.com/samber/lo"
	"github.com/ua-parser/uap-go/uaparser"
)

type PreparePasskeyLoginResponse struct {
	Options   *protocol.CredentialAssertion `json:"options"`
	SessionID string                        `json:"session_id"`
}

type UserSettings struct {
	VersionRetentionEnabled bool      `json:"version_retention_enabled"`
	VersionRetentionExt     []string  `json:"version_retention_ext,omitempty"`
	VersionRetentionMax     int       `json:"version_retention_max,omitempty"`
	Paswordless             bool      `json:"passwordless"`
	TwoFAEnabled            bool      `json:"two_fa_enabled"`
	Passkeys                []Passkey `json:"passkeys,omitempty"`
	DisableViewSync         bool      `json:"disable_view_sync"`
}

func BuildUserSettings(u *ent.User, passkeys []*ent.Passkey, parser *uaparser.Parser) *UserSettings {
	return &UserSettings{
		VersionRetentionEnabled: u.Settings.VersionRetention,
		VersionRetentionExt:     u.Settings.VersionRetentionExt,
		VersionRetentionMax:     u.Settings.VersionRetentionMax,
		TwoFAEnabled:            u.TwoFactorSecret != "",
		Paswordless:             u.Password == "",
		Passkeys: lo.Map(passkeys, func(item *ent.Passkey, index int) Passkey {
			return BuildPasskey(item)
		}),
		DisableViewSync: u.Settings.DisableViewSync,
	}
}

type Passkey struct {
	ID        string     `json:"id"`
	Name      string     `json:"name"`
	UsedAt    *time.Time `json:"used_at,omitempty"`
	CreatedAt time.Time  `json:"created_at"`
}

func BuildPasskey(passkey *ent.Passkey) Passkey {
	return Passkey{
		ID:        passkey.CredentialID,
		Name:      passkey.Name,
		UsedAt:    passkey.UsedAt,
		CreatedAt: passkey.CreatedAt,
	}
}

// Node option for handling workflows.
type Node struct {
	ID           string              `json:"id"`
	Name         string              `json:"name"`
	Type         string              `json:"type"`
	Capabilities *boolset.BooleanSet `json:"capabilities"`
}

// BuildNodes serialize a list of nodes.
func BuildNodes(nodes []*ent.Node, idEncoder hashid.Encoder) []*Node {
	res := make([]*Node, 0, len(nodes))
	for _, v := range nodes {
		res = append(res, BuildNode(v, idEncoder))
	}

	return res
}

// BuildNode serialize a node.
func BuildNode(node *ent.Node, idEncoder hashid.Encoder) *Node {
	return &Node{
		ID:           hashid.EncodeNodeID(idEncoder, node.ID),
		Name:         node.Name,
		Type:         string(node.Type),
		Capabilities: node.Capabilities,
	}
}

// BuiltinLoginResponse response for a successful login for builtin auth provider.
type BuiltinLoginResponse struct {
	User  User       `json:"user"`
	Token auth.Token `json:"token"`
}

// User 用户序列化器
type User struct {
	ID              string            `json:"id"`
	Email           string            `json:"email,omitempty"`
	Nickname        string            `json:"nickname"`
	Status          user.Status       `json:"status,omitempty"`
	Avatar          string            `json:"avatar,omitempty"`
	CreatedAt       time.Time         `json:"created_at"`
	PreferredTheme  string            `json:"preferred_theme,omitempty"`
	Anonymous       bool              `json:"anonymous,omitempty"`
	Group           *Group            `json:"group,omitempty"`
	Pined           []types.PinedFile `json:"pined,omitempty"`
	Language        string            `json:"language,omitempty"`
	DisableViewSync bool              `json:"disable_view_sync,omitempty"`
}

type Group struct {
	ID                  string              `json:"id"`
	Name                string              `json:"name"`
	Permission          *boolset.BooleanSet `json:"permission,omitempty"`
	DirectLinkBatchSize int                 `json:"direct_link_batch_size,omitempty"`
	TrashRetention      int                 `json:"trash_retention,omitempty"`
}

type storage struct {
	Used  uint64 `json:"used"`
	Free  uint64 `json:"free"`
	Total uint64 `json:"total"`
}

// WebAuthnCredentials 外部验证器凭证
type WebAuthnCredentials struct {
	ID          []byte `json:"id"`
	FingerPrint string `json:"fingerprint"`
}

type PrepareLoginResponse struct {
	WebAuthnEnabled bool `json:"webauthn_enabled"`
	PasswordEnabled bool `json:"password_enabled"`
}

// BuildWebAuthnList 构建设置页面凭证列表
func BuildWebAuthnList(credentials []webauthn.Credential) []WebAuthnCredentials {
	res := make([]WebAuthnCredentials, 0, len(credentials))
	for _, v := range credentials {
		credential := WebAuthnCredentials{
			ID:          v.ID,
			FingerPrint: fmt.Sprintf("% X", v.Authenticator.AAGUID),
		}
		res = append(res, credential)
	}

	return res
}

// BuildUser 序列化用户
func BuildUser(user *ent.User, idEncoder hashid.Encoder) User {
	return User{
		ID:              hashid.EncodeUserID(idEncoder, user.ID),
		Email:           user.Email,
		Nickname:        user.Nick,
		Status:          user.Status,
		Avatar:          user.Avatar,
		CreatedAt:       user.CreatedAt,
		PreferredTheme:  user.Settings.PreferredTheme,
		Anonymous:       user.ID == 0,
		Group:           BuildGroup(user.Edges.Group, idEncoder),
		Pined:           user.Settings.Pined,
		Language:        user.Settings.Language,
		DisableViewSync: user.Settings.DisableViewSync,
	}
}

func BuildGroup(group *ent.Group, idEncoder hashid.Encoder) *Group {
	if group == nil {
		return nil
	}
	return &Group{
		ID:                  hashid.EncodeGroupID(idEncoder, group.ID),
		Name:                group.Name,
		Permission:          group.Permissions,
		DirectLinkBatchSize: group.Settings.SourceBatchSize,
		TrashRetention:      group.Settings.TrashRetention,
	}
}

const sensitiveTag = "redacted"

const (
	RedactLevelAnonymous = iota
	RedactLevelUser
)

// BuildUserRedacted Serialize a user without sensitive information.
func BuildUserRedacted(u *ent.User, level int, idEncoder hashid.Encoder) User {
	userRaw := BuildUser(u, idEncoder)

	user := User{
		ID:        userRaw.ID,
		Nickname:  userRaw.Nickname,
		Avatar:    userRaw.Avatar,
		CreatedAt: userRaw.CreatedAt,
	}

	if userRaw.Group != nil {
		user.Group = RedactedGroup(userRaw.Group)
	}

	if level == RedactLevelUser {
		user.Email = userRaw.Email
	}

	return user
}

// BuildGroupRedacted Serialize a group without sensitive information.
func RedactedGroup(g *Group) *Group {
	if g == nil {
		return nil
	}

	return &Group{
		ID:   g.ID,
		Name: g.Name,
	}
}
