// Copyright (c) 2024 Proton AG
//
// This file is part of Proton Mail Bridge.
//
// Proton Mail Bridge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// Proton Mail Bridge is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Proton Mail Bridge.  If not, see <https://www.gnu.org/licenses/>.

package vault

import (
	"fmt"
	"strings"

	"github.com/bradenaw/juniper/xslices"
	"golang.org/x/exp/slices"
)

type User struct {
	vault  *Vault
	userID string
}

func (user *User) UserID() string {
	return user.vault.getUser(user.userID).UserID
}

func (user *User) Username() string {
	return user.vault.getUser(user.userID).Username
}

func (user *User) usernameUnsafe() string {
	return user.vault.getUserUnsafe(user.userID).Username
}

// PrimaryEmail returns the user's primary email address.
func (user *User) PrimaryEmail() string {
	return user.vault.getUser(user.userID).PrimaryEmail
}

// SetPrimaryEmail sets the user's primary email address.
func (user *User) SetPrimaryEmail(email string) error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.PrimaryEmail = email
	})
}

// GluonKey returns the key needed to decrypt the user's gluon database.
func (user *User) GluonKey() []byte {
	return user.vault.getUser(user.userID).GluonKey
}

func (user *User) GetGluonIDs() map[string]string {
	return user.vault.getUser(user.userID).GluonIDs
}

func (user *User) SetGluonID(addrID, gluonID string) error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.GluonIDs[addrID] = gluonID
	})
}

func (user *User) RemoveGluonID(addrID, gluonID string) error {
	var err error

	if modErr := user.vault.modUser(user.userID, func(data *UserData) {
		if data.GluonIDs[addrID] != gluonID {
			err = fmt.Errorf("gluon ID mismatch: %s != %s", data.GluonIDs[addrID], gluonID)
		} else {
			delete(data.GluonIDs, addrID)
		}
	}); modErr != nil {
		return modErr
	}

	return err
}

// AddressMode returns the user's address mode.
func (user *User) AddressMode() AddressMode {
	return user.vault.getUser(user.userID).AddressMode
}

// SetAddressMode sets the address mode for the given user.
func (user *User) SetAddressMode(mode AddressMode) error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.AddressMode = mode
	})
}

// BridgePass returns the user's bridge password as raw token bytes (unencoded).
func (user *User) BridgePass() []byte {
	return user.vault.getUser(user.userID).BridgePass
}

// SetBridgePass saves bridge password as raw token bytes (unecoded).
func (user *User) SetBridgePass(newPass []byte) error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.BridgePass = newPass
	})
}

// AuthUID returns the user's auth UID.
func (user *User) AuthUID() string {
	return user.vault.getUser(user.userID).AuthUID
}

// AuthRef returns the user's auth refresh token.
func (user *User) AuthRef() string {
	return user.vault.getUser(user.userID).AuthRef
}

// SetAuth sets the auth secrets for the given user.
func (user *User) SetAuth(authUID, authRef string) error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.AuthUID = authUID
		data.AuthRef = authRef
	})
}

func (user *User) setAuthAndKeyPassUnsafe(authUID, authRef string, keyPass []byte) error {
	return user.vault.modUserUnsafe(user.userID, func(userData *UserData) {
		userData.AuthRef = authRef
		userData.AuthUID = authUID
		userData.KeyPass = keyPass
	})
}

// KeyPass returns the user's (salted) key password.
func (user *User) KeyPass() []byte {
	return user.vault.getUser(user.userID).KeyPass
}

// SetKeyPass sets the user's (salted) key password.
func (user *User) SetKeyPass(keyPass []byte) error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.KeyPass = keyPass
	})
}

// SyncStatus return's the user's sync status.
func (user *User) SyncStatus() SyncStatus {
	return user.vault.getUser(user.userID).SyncStatus
}

// SetHasLabels sets whether the user's labels have been synced.
func (user *User) SetHasLabels(hasLabels bool) error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.SyncStatus.HasLabels = hasLabels
	})
}

// SetHasMessages sets whether the user's messages have been synced.
func (user *User) SetHasMessages(hasMessages bool) error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.SyncStatus.HasMessages = hasMessages
	})
}

// SetLastMessageID sets the last synced message ID for the given user.
func (user *User) SetLastMessageID(messageID string) error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.SyncStatus.LastMessageID = messageID
	})
}

// AddFailedMessageID adds a message ID to the list of failed message IDs.
func (user *User) AddFailedMessageID(messageID string) error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		if !slices.Contains(data.SyncStatus.FailedMessageIDs, messageID) {
			data.SyncStatus.FailedMessageIDs = append(data.SyncStatus.FailedMessageIDs, messageID)
		}
	})
}

// RemFailedMessageID removes a message ID from the list of failed message IDs.
func (user *User) RemFailedMessageID(messageID string) error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.SyncStatus.FailedMessageIDs = xslices.Filter(data.SyncStatus.FailedMessageIDs, func(otherID string) bool {
			return otherID != messageID
		})
	})
}

// GetSyncStatusDeprecated returns the user's sync status.
func (user *User) GetSyncStatusDeprecated() SyncStatus {
	return user.vault.getUser(user.userID).SyncStatus
}

// ClearSyncStatusDeprecated clears the user's sync status.
func (user *User) ClearSyncStatusDeprecated() error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.SyncStatus = SyncStatus{}

		data.EventID = ""
	})
}

// ClearSyncStatusWithoutEventID clears the user's sync status without modifying EventID.
func (user *User) ClearSyncStatusWithoutEventID() error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.SyncStatus = SyncStatus{}
	})
}

// EventID returns the last processed event ID of the user.
func (user *User) EventID() string {
	return user.vault.getUser(user.userID).EventID
}

// SetEventID sets the event ID for the given user.
func (user *User) SetEventID(eventID string) error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.EventID = eventID
	})
}

// Clear clears the user's auth secrets.
func (user *User) Clear() error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.AuthUID = ""
		data.AuthRef = ""
		data.KeyPass = nil
	})
}

// Close closes the user. This allows it to be removed from the vault.
func (user *User) Close() error {
	return user.vault.detachUser(user.userID)
}

func (user *User) SetShouldSync(shouldResync bool) error {
	return user.vault.modUser(user.userID, func(data *UserData) {
		data.ShouldResync = shouldResync
	})
}

func (user *User) GetShouldResync() bool {
	return user.vault.getUser(user.userID).ShouldResync
}

// updateUsernameUnsafe - updates the username of the relevant user, provided that the new username is not empty
// and differs from the previous. Writes are not performed if this case is not met.
// Should only be called from contexts where the vault mutex is already locked.
func (user *User) updateUsernameUnsafe(username string) error {
	if strings.TrimSpace(username) == "" || user.usernameUnsafe() == username {
		return nil
	}
	return user.vault.modUserUnsafe(user.userID, func(userData *UserData) {
		userData.Username = username
	})
}
