// SPDX-FileCopyrightText: 2018 - 2022 UnionTech Software Technology Co., Ltd.
//
// SPDX-License-Identifier: GPL-3.0-or-later

package grub2

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"regexp"
	"sync"

	"github.com/linuxdeepin/go-lib/dbusutil"
)

// EditAuth is a dbus object which provide properties and methods to
// setup deepin grub2 edit shell authorization.
type EditAuth struct {
	g               *Grub2
	service         *dbusutil.Service
	b               *bytes.Buffer
	data            []byte
	buffer          []byte
	userAuthInfoMap map[string][]byte
	configMu        sync.Mutex
	configFile      string
	testMode        bool
	reg             *regexp.Regexp
	// dbusutil-gen: equal=nil
	EnabledUsers []string
}

const (
	uosMenuCryptoFile = "/etc/grub.d/42_uos_menu_crypto"
	pbkdf2Prefix      = "password_pbkdf2"
)

// NewEditAuth create EditAuth object.
func NewEditAuth(g *Grub2) *EditAuth {
	return &EditAuth{
		g:               g,
		service:         g.service,
		buffer:          make([]byte, 0, 4096),
		userAuthInfoMap: map[string][]byte{},
		configFile:      uosMenuCryptoFile,
		testMode:        false,
		reg:             regexp.MustCompile(`^[a-zA-Z0-9_-]+$`),
		EnabledUsers:    []string{},
	}
}

func (e *EditAuth) init() {
	_, err := os.Stat(e.configFile)
	if err != nil && os.IsNotExist(err) {
		return
	}

	logger.Debugf("load data from %s", e.configFile)
	data, err := ioutil.ReadFile(e.configFile)
	if err != nil {
		logger.Warning(err)
	}

	e.data = data
	e.load()
	for k := range e.userAuthInfoMap {
		e.EnabledUsers = append(e.EnabledUsers, k)
	}
}

// loadUserAuthInfo 从配置文件解析数据到缓存结构
func (e *EditAuth) loadUserAuthInfo() error {
	_, err := os.Stat(e.configFile)
	if err != nil && os.IsNotExist(err) {
		logger.Debugf("file %s is not exist", e.configFile)
		return nil
	}

	logger.Debugf("load data from %s", e.configFile)
	data, err := ioutil.ReadFile(e.configFile)
	if err != nil {
		return err
	}

	e.data = data
	e.load()
	e.updatePropertyUser()

	return err
}

// saveUserAuthInfo 将缓存结构信息写入配置文件
func (e *EditAuth) saveUserAuthInfo() error {
	const head = `#!/bin/sh
exec tail -n +3 $0
#This file is automatically generated by dde-daemon, do not edit it directly.

`

	b := bytes.NewBuffer(e.buffer[:0])
	b.WriteString(head)

	for _, v := range e.userAuthInfoMap {
		b.Write(v)
	}

	return ioutil.WriteFile(e.configFile, b.Bytes(), 0755)
}

func (e *EditAuth) load() {
	e.b = bytes.NewBuffer(e.data)
	e.userAuthInfoMap = make(map[string][]byte)
	for {
		line, err := e.b.ReadBytes('\n')
		if err != nil {
			if err != io.EOF {
				logger.Warning(err)
				break
			}

			if len(line) == 0 {
				break
			}
		}
		fields := bytes.Fields(line)
		if len(fields) < 3 || fields[0][0] == '#' {
			continue
		}

		if !bytes.Equal(fields[0], []byte(pbkdf2Prefix)) {
			continue
		}

		e.userAuthInfoMap[string(fields[1])] = line
	}
}

// rollback 回滚配置数据
func (e *EditAuth) rollback() error {
	logger.Debug("rollback file data")
	e.load()
	return e.saveUserAuthInfo()
}

// updateUserAuthInfo 更新用户认证缓存信息
func (e *EditAuth) updateUserAuthInfo(user, password string) {
	line := fmt.Sprintln(pbkdf2Prefix, user, password)
	e.userAuthInfoMap[user] = []byte(line)
}

// delUserAuthInfo 删除用户认证信息
func (e *EditAuth) delUserAuthInfo(user string) error {
	if _, ok := e.userAuthInfoMap[user]; ok {
		delete(e.userAuthInfoMap, user)
		return nil
	}

	return fmt.Errorf("can't find user: %s", user)
}

func (e *EditAuth) hasPropertyUserChanged() bool {
	if len(e.userAuthInfoMap) != len(e.EnabledUsers) {
		return true
	}

	for _, v := range e.EnabledUsers {
		if _, ok := e.userAuthInfoMap[v]; !ok {
			return true
		}
	}

	return false
}

func (e *EditAuth) updatePropertyUser() {
	if e.hasPropertyUserChanged() {
		e.EnabledUsers = e.EnabledUsers[:0]
		for k := range e.userAuthInfoMap {
			e.EnabledUsers = append(e.EnabledUsers, k)
		}

		if e.service != nil {
			err := e.emitPropChangedEnabledUsers(e.EnabledUsers)
			if err != nil {
				logger.Warning(err)
			}
		}
	}
}

// setGrubEditShellAuth 安装GRUB菜单编辑用户认证
func (e *EditAuth) setGrubEditShellAuth(user, password string) error {
	e.configMu.Lock()
	defer e.configMu.Unlock()

	err := e.loadUserAuthInfo()
	if err != nil {
		return err
	}

	e.updateUserAuthInfo(user, password)
	err = e.saveUserAuthInfo()
	if err != nil {
		logger.Warning(err)
		if err := e.rollback(); err != nil {
			logger.Warning(err)
		}
		return err
	}

	if !e.testMode {
		e.g.addModifyTask(modifyTask{})
	}

	e.updatePropertyUser()

	return nil
}

// disableGrubEditShellAuth 撤销GRUB菜单编辑用户认证
func (e *EditAuth) disableGrubEditShellAuth(user string) error {
	e.configMu.Lock()
	defer e.configMu.Unlock()

	err := e.loadUserAuthInfo()
	if err != nil {
		return err
	}

	err = e.delUserAuthInfo(user)
	if err != nil {
		logger.Warning(err)
		return err
	}
	err = e.saveUserAuthInfo()
	if err != nil {
		logger.Warning(err)
		if err := e.rollback(); err != nil {
			logger.Error(err)
		}
		return err
	}

	if !e.testMode {
		e.g.addModifyTask(modifyTask{})
	}

	e.updatePropertyUser()

	return nil
}
