/*
 * Copyright (C) 2013 ~ 2018 Deepin Technology Co., Ltd.
 *
 * Author:     jouyouyun <jouyouwen717@gmail.com>
 *
 * This program 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
 * any later version.
 *
 * This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package accounts

import (
	"errors"
	"fmt"
	"math/rand"
	"os"
	"strconv"
	"time"

	"github.com/godbus/dbus"
	"github.com/linuxdeepin/dde-daemon/accounts/checkers"
	"github.com/linuxdeepin/dde-daemon/accounts/users"
	"github.com/linuxdeepin/go-lib/dbusutil"
	"github.com/linuxdeepin/go-lib/gettext"
	"github.com/linuxdeepin/go-lib/procfs"
	"github.com/linuxdeepin/go-lib/users/passwd"
	dutils "github.com/linuxdeepin/go-lib/utils"
)

const (
	nilObjPath      = dbus.ObjectPath("/")
	dbusServiceName = "com.deepin.daemon.Accounts"
	dbusPath        = "/com/deepin/daemon/Accounts"
	dbusInterface   = "com.deepin.daemon.Accounts"
)

func (*Manager) GetInterfaceName() string {
	return dbusInterface
}

// Create new user.
//
// 如果收到 Error 信号，则创建失败。
//
// name: 用户名
//
// fullName: 全名，可以为空
//
// ty: 用户类型，0 为普通用户，1 为管理员

func (m *Manager) CreateUser(sender dbus.Sender,
	name, fullName string, accountType int32) (userPath dbus.ObjectPath, busErr *dbus.Error) {

	logger.Debug("[CreateUser] new user:", name, fullName, accountType)

	err := checkAccountType(int(accountType))
	if err != nil {
		return nilObjPath, dbusutil.ToError(err)
	}

	err = m.checkAuth(sender)
	if err != nil {
		logger.Debug("[CreateUser] access denied:", err)
		return nilObjPath, dbusutil.ToError(err)
	}

	ch := make(chan string)
	m.usersMapMu.Lock()
	m.userAddedChanMap[name] = ch
	m.usersMapMu.Unlock()
	defer func() {
		m.usersMapMu.Lock()
		delete(m.userAddedChanMap, name)
		m.usersMapMu.Unlock()
		close(ch)
	}()

	homeDir := "/home/" + name
	_, err = os.Stat(homeDir)
	homeDirExist := err == nil

	if err := users.CreateUser(name, fullName, ""); err != nil {
		logger.Warningf("DoAction: create user '%s' failed: %v\n",
			name, err)
		return nilObjPath, dbusutil.ToError(err)
	}

	groups := users.GetPresetGroups(int(accountType))
	logger.Debug("groups:", groups)
	err = users.SetGroupsForUser(groups, name)
	if err != nil {
		logger.Warningf("failed to set groups for user %s: %v", name, err)
	}

	// create user success
	select {
	case userPath, ok := <-ch:
		if !ok {
			return nilObjPath, dbusutil.ToError(errors.New("invalid user path event"))
		}

		logger.Debug("receive user path", userPath)
		if userPath == "" {
			return nilObjPath, dbusutil.ToError(errors.New("failed to install user on session bus"))
		}
		if homeDirExist {
			go chownHomeDir(homeDir, name)
		}
		return dbus.ObjectPath(userPath), nil
	case <-time.After(time.Second * 60):
		err := errors.New("wait timeout exceeded")
		logger.Warning(err)
		return nilObjPath, dbusutil.ToError(err)
	}
}

// Delete a exist user.
//
// name: 用户名
//
// rmFiles: 是否删除用户数据
func (m *Manager) DeleteUser(sender dbus.Sender,
	name string, rmFiles bool) *dbus.Error {

	logger.Debug("[DeleteUser] user:", name, rmFiles)

	err := m.checkAuth(sender)
	if err != nil {
		logger.Debug("[DeleteUser] access denied:", err)
		return dbusutil.ToError(err)
	}

	user := m.getUserByName(name)
	if user == nil {
		err := fmt.Errorf("user %q not found", name)
		logger.Warning(err)
		return dbusutil.ToError(err)
	}

	if m.isUdcpUserID(user.Uid) {
		id, _ := strconv.Atoi(user.Uid)
		result, err := m.udcpCache.RemoveCacheFile(0, uint32(id))
		if err != nil {
			logger.Errorf("Udcp cache RemoveCacheFile failed: %v", err)
			return dbusutil.ToError(err)
		}

		if !result {
			return dbusutil.ToError(errors.New("failed to remove user cache files"))
		}

		// 删除账户前先删除生物特征，避免删除账户后，用户数据找不到
		if rmFiles {
			user.clearBiometricChara()
		}

		// 删除服务，更新UserList
		userPath := userDBusPathPrefix + user.Uid
		m.stopExportUser(userPath)
		m.updatePropUserList()

		// 清楚域账户本地缓存
		if rmFiles {
			user.clearData()
		}

		err = m.service.Emit(m, "UserDeleted", userPath)
		if err != nil {
			logger.Warning(err)
		}
		return dbusutil.ToError(err)
	}

	// 删除账户前先删除生物特征，避免删除账户后，用户数据找不到
	if rmFiles {
		user.clearBiometricChara()
	}
	if err := users.DeleteUser(rmFiles, name); err != nil {
		logger.Warningf("DoAction: delete user '%s' failed: %v\n",
			name, err)
		return dbusutil.ToError(err)
	}

	if users.IsAutoLoginUser(name) {
		_ = users.SetAutoLoginUser("", "")
	}

	//delete user config and icons
	if rmFiles {
		user.clearData()
	}
	return nil
}

func (m *Manager) FindUserById(uid string) (user string, busErr *dbus.Error) {
	userPath := userDBusPathPrefix + uid
	for _, v := range m.UserList {
		if v == userPath {
			return v, nil
		}
	}

	return "", dbusutil.ToError(fmt.Errorf("invalid uid: %s", uid))
}

func (m *Manager) FindUserByName(name string) (user string, busErr *dbus.Error) {
	pwd, err := passwd.GetPasswdByName(name)
	if err != nil {
		logger.Warning(err)
		pwd = &passwd.Passwd{
			Name: name,
		}
	}

	m.usersMapMu.Lock()
	defer m.usersMapMu.Unlock()

	for p, u := range m.usersMap {
		if u.UserName == pwd.Name {
			return p, nil
		}
	}

	return "", dbusutil.ToError(fmt.Errorf("invalid username: %s", pwd.Name))
}

// 随机得到一个用户头像
//
// ret0：头像路径，为空则表示获取失败
func (m *Manager) RandUserIcon() (iconFile string, busErr *dbus.Error) {
	icons := getUserStandardIcons()
	if len(icons) == 0 {
		return "", dbusutil.ToError(errors.New("Did not find any user icons"))
	}

	rand.Seed(time.Now().UnixNano())
	idx := rand.Intn(len(icons)) // #nosec G404
	return icons[idx], nil
}

// 检查用户名是否有效
//
// ret0: 是否合法
//
// ret1: 不合法原因
//
// ret2: 不合法代码
func (m *Manager) IsUsernameValid(sender dbus.Sender, name string) (valid bool,
	msg string, code int32, busErr *dbus.Error) {

	var err error
	defer func() {
		busErr = dbusutil.ToError(err)
	}()

	pid, err := m.service.GetConnPID(string(sender))
	if err != nil {
		return
	}

	p := procfs.Process(pid)
	environ, err := p.Environ()
	if err != nil {
		return
	}

	locale := environ.Get("LANG")

	info := checkers.CheckUsernameValid(name)
	if info == nil {
		valid = true
		return
	}

	msg = info.Error.Error()
	logger.Debug("locale:", locale)
	if locale != "" {
		gettext.SetLocale(gettext.LcAll, locale)
		msg = gettext.Tr(msg)
	}
	code = int32(info.Code)
	return
}

// 检测密码是否有效
//
// ret0: 是否合法
//
// ret1: 提示信息
//
// ret2: 不合法代码
func (m *Manager) IsPasswordValid(password string) (valid bool, msg string, code int32, busErr *dbus.Error) {
	releaseType := getDeepinReleaseType()
	logger.Infof("release type %q", releaseType)
	errCode := checkers.CheckPasswordValid(releaseType, password)
	return errCode.IsOk(), errCode.Prompt(), int32(errCode), nil
}

func (m *Manager) AllowGuestAccount(sender dbus.Sender, allow bool) *dbus.Error {
	err := m.checkAuth(sender)
	if err != nil {
		return dbusutil.ToError(err)
	}

	m.PropsMu.Lock()
	defer m.PropsMu.Unlock()

	if m.AllowGuest == allow {
		return nil
	}

	success := dutils.WriteKeyToKeyFile(actConfigFile,
		actConfigGroupGroup, actConfigKeyGuest, allow)
	if !success {
		return dbusutil.ToError(errors.New("enable guest user failed"))
	}

	m.AllowGuest = allow
	_ = m.emitPropChangedAllowGuest(allow)
	return nil
}

func (m *Manager) CreateGuestAccount(sender dbus.Sender) (user string, busErr *dbus.Error) {
	err := m.checkAuth(sender)
	if err != nil {
		return "", dbusutil.ToError(err)
	}

	name, err := users.CreateGuestUser()
	if err != nil {
		return "", dbusutil.ToError(err)
	}

	info, err := users.GetUserInfoByName(name)
	if err != nil {
		return "", dbusutil.ToError(err)
	}

	return userDBusPathPrefix + info.Uid, nil
}

func (m *Manager) GetGroups() (groups []string, busErr *dbus.Error) {
	groups, err := users.GetAllGroups()
	return groups, dbusutil.ToError(err)
}

func (m *Manager) GetPresetGroups(accountType int32) (groups []string, busErr *dbus.Error) {
	err := checkAccountType(int(accountType))
	if err != nil {
		return nil, dbusutil.ToError(err)
	}

	groups = users.GetPresetGroups(int(accountType))
	return groups, nil
}

// 是否使能accounts服务在监听到/etc/passwd文件变化后,执行对应的属性更新和服务导出,只允许root用户操作该接口
func (m *Manager) EnablePasswdChangedHandler(sender dbus.Sender, enable bool) *dbus.Error {
	const rootUid = "0"
	uid, err := m.service.GetConnUID(string(sender))
	if err != nil {
		return dbusutil.ToError(err)
	}
	if uid != 0 {
		return dbusutil.ToError(fmt.Errorf("not allow %v call this method", sender))
	}
	m.enablePasswdChangedHandlerMu.Lock()
	defer m.enablePasswdChangedHandlerMu.Unlock()
	if m.enablePasswdChangedHandler == enable {
		return nil
	}
	m.enablePasswdChangedHandler = enable
	if enable {
		m.handleFilePasswdChanged()
		m.modifyUserConfig(userDBusPathPrefix + rootUid) // root账户的信息需要更新（deepin安装器会在后配置界面修改语言）
	}
	return nil
}

func (m *Manager) modifyUserConfig(path string) error {
	m.usersMapMu.Lock()
	root, ok := m.usersMap[path]
	m.usersMapMu.Unlock()
	if ok {
		loadUserConfigInfo(root)
	}

	return nil
}
