/*
 * Copyright (c) KylinSoft  Co., Ltd. 2024.All rights reserved.
 * ha-api licensed under the Mulan Permissive Software License, Version 2.
 * See LICENSE file for more details.
 * Author: bizhiyuan <bizhiyuan@kylinos.cn>
 * Date: Tue Mar 12 10:13:58 2024 +0800
 */
package models

import (
	"fmt"
	"log/slog"

	"gitee.com/openeuler/ha-api/utils"
	"gitee.com/openeuler/ha-api/validations"
	"github.com/beevik/etree"
	"github.com/chai2010/gettext-go"
)

type Rule struct {
	Rsc       string `json:"rsc"`
	RuleId    string `json:"ruleid"`
	Score     string `json:"score"`
	Attribute string `json:"attribute"`
	Operation string `json:"operation"`
	Value     string `json:"value"`
}

type RuleGetResponse struct {
	utils.GeneralResponse
	Data []Rule `json:"data"`
}

func RulesGet(rscName string) RuleGetResponse {

	var doc *etree.Document
	var rules []*etree.Element

	rulelist := []Rule{}

	out, err := utils.RunCommand(utils.CmdQueryConstraints)
	if err != nil {
		goto ret
	}

	doc = etree.NewDocument()
	if err = doc.ReadFromBytes(out); err != nil {
		slog.Error("RulesGet: Read xml failed", "error", err)
		goto ret
	}

	rules = doc.FindElements("/constraints/rsc_location")
	for _, ruleElem := range rules {
		if rsc := ruleElem.SelectAttrValue("rsc", ""); rsc == rscName {
			rule := ruleElem.SelectElement("rule")
			if rule != nil {
				expr := rule.SelectElement("expression")
				r := Rule{
					Rsc:    rsc,
					RuleId: rule.SelectAttrValue("id", ""),
					Score:  rule.SelectAttrValue("score", ""),
				}
				if expr != nil {
					r.Attribute = expr.SelectAttrValue("attribute", "")
					r.Operation = expr.SelectAttrValue("operation", "")
					r.Value = expr.SelectAttrValue("value", "")
				}
				rulelist = append(rulelist, r)
			}

		}
	}

	return RuleGetResponse{
		Data: rulelist,
		GeneralResponse: utils.GeneralResponse{
			Action: true,
		},
	}

ret:
	return RuleGetResponse{
		GeneralResponse: utils.GeneralResponse{
			Action: false,
			Error:  gettext.Gettext("Get rule failed"),
		},
	}
}

type RuleDeleteResponse struct {
	Action bool                     `json:"action"`
	Info   string                   `json:"info,omitempty"`
	Error  []map[string]interface{} `json:"error,omitempty"`
}

// Todo:return list ?
func RulesDelete(ruleids *validations.DeleteRuleS) RuleDeleteResponse {
	ruleIdList := ruleids.RuleIDs
	slog.Debug("RulesDelete", "ids", ruleIdList)
	var res []map[string]interface{}

	for _, id := range ruleIdList {
		delRuleCmd := fmt.Sprintf(utils.CmdRuleDelete, utils.ShellEscape(id))
		_, err := utils.RunCommand(delRuleCmd)
		if err != nil {
			res = append(res, map[string]interface{}{
				"id":    id,
				"error": fmt.Sprintf(gettext.Gettext("Rule %s could not be found"), id),
			})
		}
	}
	if len(res) != 0 {
		// Todo: some rules was deleted failed, recovery?
		return RuleDeleteResponse{
			Action: false,
			Error:  res,
		}
	}
	return RuleDeleteResponse{
		Action: true,
		Info:   gettext.Gettext("Delete rule success"),
	}
}

func RuleAdd(data *validations.RuleS) utils.GeneralResponse {
	slog.Debug("RuleAdd",
		"id", data.RuleID,
		"value", data.Value,
	)
	var cmdAddRule string
	if data.RuleID != "" {
		cmdAddRule = fmt.Sprintf(utils.CmdRuleAddWithId, utils.ShellEscape(data.Rsc), utils.ShellEscape(data.Score), utils.ShellEscape(data.RuleID))
	} else {
		cmdAddRule = fmt.Sprintf(utils.CmdRuleAdd, utils.ShellEscape(data.Rsc), utils.ShellEscape(data.Score))
	}

	if data.Value != "" {
		cmdAddRule = cmdAddRule + " " + utils.ShellEscape(data.Attribute) + " " + utils.ShellEscape(data.Operation) + " " + utils.ShellEscape(data.Value)
	} else {
		cmdAddRule = cmdAddRule + " " + utils.ShellEscape(data.Operation) + " " + utils.ShellEscape(data.Attribute)
	}
	_, err := utils.RunCommand(cmdAddRule)
	if err != nil {
		return utils.HandleCmdError(gettext.Gettext("Add rule failed, duplicate constraint already exists"), false)
	}
	slog.Info("Add rule success", "id", data.RuleID)
	return utils.GeneralResponse{
		Action: true,
		Info:   gettext.Gettext("Add rule success"),
	}
}

func RuleUpdate(data *validations.RuleS) utils.GeneralResponse {
	out, err := utils.RunCommand(utils.CmdQueryConstraints)
	if err != nil {
		return utils.HandleCmdError(err.Error(), false)
	}

	oldRule := new(validations.RuleS)
	doc := etree.NewDocument()
	if err := doc.ReadFromBytes(out); err != nil {
		return utils.HandleXmlError(err.Error(), false)
	}
	rules := doc.FindElements("//rsc_location/rule")
	for _, ruleElem := range rules {
		ruleId := ruleElem.SelectAttrValue("id", "")
		if ruleId == data.RuleID {
			if parent := ruleElem.Parent(); parent != nil {
				oldRule.Rsc = parent.SelectAttrValue("rsc", "")
			}
			oldRule.RuleID = ruleElem.SelectAttrValue("id", "")
			oldRule.Score = ruleElem.SelectAttrValue("score", "")
			if expr := ruleElem.SelectElement("expression"); expr != nil {
				oldRule.Attribute = expr.SelectAttrValue("attribute", "")
				oldRule.Operation = expr.SelectAttrValue("operation", "")
				oldRule.Value = expr.SelectAttrValue("value", "")
			}
		}
	}
	// delete old rule
	deleteRuleCmd := fmt.Sprintf(utils.CmdRuleDelete, utils.ShellEscape(data.RuleID))
	_, err = utils.RunCommand(deleteRuleCmd)
	if err != nil {
		return utils.HandleCmdError(
			fmt.Sprintf(gettext.Gettext("Update rule failed, rule %s not found"), data.RuleID), false)
	}

	// add new rule
	resp := RuleAdd(data)
	if !resp.Action {
		// recovery the update op
		RuleAdd(oldRule)
		return utils.HandleCmdError(gettext.Gettext("Update rule failed, duplicate constraint already exists"), false)
	}
	slog.Info("Update rule success", "id", data.RuleID)
	return utils.GeneralResponse{
		Action: true,
		Info:   gettext.Gettext("Update rule success"),
	}
}