package sys_casbin

import (
	"context"
	"kami/utility/config"
	"sync"

	"kami/internal/dao"
	"kami/internal/model/entity"
	"kami/internal/service"

	"github.com/casbin/casbin/v2"
	"github.com/casbin/casbin/v2/model"
	"github.com/casbin/casbin/v2/persist"
	"github.com/gogf/gf/v2/frame/g"
	"github.com/gogf/gf/v2/os/gctx"
)

func init() {
	service.RegisterCasbin(New())
}

func New() *sCasbin {
	a := new(sCasbin)
	once.Do(func() {
		en, enErr = initPolicy(gctx.GetInitCtx(), a)
	})
	if enErr == nil && en != nil {
		en.SetAdapter(a)
	}
	a.Enforcer, a.EnforcerErr = en, enErr
	return &sCasbin{}
}

type sCasbin struct {
	Enforcer    *casbin.SyncedEnforcer
	EnforcerErr error
	Ctx         context.Context
}

var (
	once  sync.Once
	en    *casbin.SyncedEnforcer
	enErr error
)

// CasbinEnforcer 获取adapter单例对象
func (s *sCasbin) CasbinEnforcer(ctx context.Context) (enforcer *casbin.SyncedEnforcer, err error) {
	enforcer = s.Enforcer
	err = s.EnforcerErr
	s.Ctx = ctx
	return
}

func initPolicy(ctx context.Context, a *sCasbin) (e *casbin.SyncedEnforcer, err error) {
	e, err = casbin.NewSyncedEnforcer(g.Cfg().MustGet(ctx, "casbin.modelFile").String(), a)
	return
}

// SavePolicy saves policy to database.
func (s *sCasbin) SavePolicy(model model.Model) (err error) {
	err = s.dropTable()
	if err != nil {
		return
	}
	err = s.createTable()
	if err != nil {
		return
	}
	for pType, ast := range model["p"] {
		for _, rule := range ast.Policy {
			line := savePolicyLine(pType, rule)
			_, err = dao.V1SysCasbinRule.Ctx(s.Ctx).Data(line).Insert()
			if err != nil {
				return err
			}
		}
	}
	for pType, ast := range model["g"] {
		for _, rule := range ast.Policy {
			line := savePolicyLine(pType, rule)
			_, err = dao.V1SysCasbinRule.Ctx(s.Ctx).Data(line).Insert()
			if err != nil {
				return err
			}
		}
	}
	return
}

func (s *sCasbin) dropTable() (err error) {
	return
}

func (s *sCasbin) createTable() (err error) {
	return
}

// LoadPolicy loads policy from database.
func (s *sCasbin) LoadPolicy(model model.Model) error {
	var lines []*entity.V1SysCasbinRule
	if err := dao.V1SysCasbinRule.Ctx(s.Ctx).DB(config.GetDatabaseV1()).Scan(&lines); err != nil {
		return err
	}
	for _, line := range lines {
		loadPolicyLine(line, model)
	}
	return nil
}

// AddPolicy adds a policy rule to the storage.
func (s *sCasbin) AddPolicy(sec string, pType string, rule []string) error {
	line := savePolicyLine(pType, rule)
	_, err := dao.V1SysCasbinRule.Ctx(s.Ctx).Data(line).Insert()
	return err
}

// RemovePolicy removes a policy rule from the storage.
func (s *sCasbin) RemovePolicy(sec string, pType string, rule []string) error {
	line := savePolicyLine(pType, rule)
	err := rawDelete(s.Ctx, line)
	return err
}

func (s *sCasbin) AddPolicies(sec, pType string, rules [][]string) error {
	lines := make([]*entity.V1SysCasbinRule, len(rules))
	for k, rule := range rules {
		lines[k] = savePolicyLine(pType, rule)
	}
	_, err := dao.V1SysCasbinRule.Ctx(s.Ctx).Data(lines).Insert()
	return err
}

// RemovePolicies removes policy rules from the storage.
// This is part of the Auto-Save feature.
func (s *sCasbin) RemovePolicies(sec string, ptype string, rules [][]string) error {
	for _, rule := range rules {
		err := s.RemovePolicy(sec, ptype, rule)
		if err != nil {
			return err
		}
	}
	return nil
}

// RemoveFilteredPolicy removes policy rules that match the filter from the storage.
func (s *sCasbin) RemoveFilteredPolicy(sec, ptype string,
	fieldIndex int, fieldValues ...string,
) error {
	line := &entity.V1SysCasbinRule{}
	line.Ptype = ptype
	if fieldIndex <= 0 && 0 < fieldIndex+len(fieldValues) {
		line.V0 = fieldValues[0-fieldIndex]
	}
	if fieldIndex <= 1 && 1 < fieldIndex+len(fieldValues) {
		line.V1 = fieldValues[1-fieldIndex]
	}
	if fieldIndex <= 2 && 2 < fieldIndex+len(fieldValues) {
		line.V2 = fieldValues[2-fieldIndex]
	}
	if fieldIndex <= 3 && 3 < fieldIndex+len(fieldValues) {
		line.V3 = fieldValues[3-fieldIndex]
	}
	if fieldIndex <= 4 && 4 < fieldIndex+len(fieldValues) {
		line.V4 = fieldValues[4-fieldIndex]
	}
	if fieldIndex <= 5 && 5 < fieldIndex+len(fieldValues) {
		line.V5 = fieldValues[5-fieldIndex]
	}
	err := rawDelete(s.Ctx, line)
	return err
}

func loadPolicyLine(line *entity.V1SysCasbinRule, model model.Model) (err error) {
	lineText := line.Ptype
	if line.V0 != "" {
		lineText += ", " + line.V0
	}
	if line.V1 != "" {
		lineText += ", " + line.V1
	}
	if line.V2 != "" {
		lineText += ", " + line.V2
	}
	if line.V3 != "" {
		lineText += ", " + line.V3
	}
	if line.V4 != "" {
		lineText += ", " + line.V4
	}
	if line.V5 != "" {
		lineText += ", " + line.V5
	}
	err = persist.LoadPolicyLine(lineText, model)
	return
}

func savePolicyLine(pType string, rule []string) *entity.V1SysCasbinRule {
	line := &entity.V1SysCasbinRule{}
	line.Ptype = pType
	if len(rule) > 0 {
		line.V0 = rule[0]
	}
	if len(rule) > 1 {
		line.V1 = rule[1]
	}
	if len(rule) > 2 {
		line.V2 = rule[2]
	}
	if len(rule) > 3 {
		line.V3 = rule[3]
	}
	if len(rule) > 4 {
		line.V4 = rule[4]
	}
	if len(rule) > 5 {
		line.V5 = rule[5]
	}
	return line
}

func rawDelete(ctx context.Context, line *entity.V1SysCasbinRule) error {
	db := dao.V1SysCasbinRule.Ctx(ctx).Where(dao.V1SysCasbinRule.Columns().Ptype, line.Ptype)
	if line.V0 != "" {
		db = db.Where(dao.V1SysCasbinRule.Columns().V0, line.V0)
	}
	if line.V1 != "" {
		db = db.Where(dao.V1SysCasbinRule.Columns().V1, line.V1)
	}
	if line.V2 != "" {
		db = db.Where(dao.V1SysCasbinRule.Columns().V2, line.V2)
	}
	if line.V3 != "" {
		db = db.Where(dao.V1SysCasbinRule.Columns().V3, line.V3)
	}
	if line.V4 != "" {
		db = db.Where(dao.V1SysCasbinRule.Columns().V4, line.V4)
	}
	if line.V5 != "" {
		db = db.Where(dao.V1SysCasbinRule.Columns().V5, line.V5)
	}
	_, err := db.Delete()
	return err
}
