package rbac

import (
	"errors"
	"fmt"
	"math"
	"strconv"
	"strings"
)

// rbac wrapper function: curd policy group, curd domain, user, role
// model: curd policy group, curd domain, user, role
// org manage: create org, list org
// api

// create role in domain access data
// 1. user inherit
// 2. data inherit

const Admin = "admin"

const (
	ActRead    = "read"
	ActWrite   = "write"
	ActAnalyze = "analyze"
	ActAny     = "*"
)

const (
	RoleAdmin       = "admin"        // to modify user under org or delete org itself
	RoleViewer      = "viewer"       // to access tidb cluster data
	RoleEditor      = "editor"       // to modify cluster under org
	RoleAnalysts    = "analysts"     // to rebuild and generate report under org
	RolePingCAPer   = "pingcaper"    // role assign to all pingcaper
	RoleClinicOps   = "clinic-ops"   // to use drill test under org
	RoleClinicAdmin = "clinic-admin" // to enable or disable drill test feature under org
)

var ActingBits = map[Acting]int{
	"read":    1,
	"write":   4,
	"analyze": 3,
}

// TODO: change to string
type Acting string

func (a Acting) ToString() string {
	return string(a)
	// return strconv.Itoa(int(a))
}

func (a Acting) ToBits() int {
	if bits, ok := ActingBits[a]; ok {
		return bits
	}
	bits, _ := strconv.Atoi(string(a))
	return bits
}

func ReadActing() Acting {
	return "read"
	// return 1
}

func WriteActing() Acting {
	return "write"
	// return 1 << 2
}

func ApproveActing() Acting {
	return "read"
	// return 1
}

func DeleteActing() Acting {
	return "write"
	// return 1 << 2
}

// TODO
func RebuildActing() Acting {
	return "analyze"
	// return 3
}

func ActingMatchFunc(args ...any) (any, error) {
	if len(args) != 2 {
		return nil, errors.New("acting match func only accept two arguments")
	}
	if pAct, err := strconv.Atoi(args[1].(string)); err == nil {
		rAct := Acting(args[0].(string)).ToBits()
		return ActingMatchByBits(rAct, pAct), nil
	}
	return ActingMatchByName(args[0].(string), args[1].(string)), nil
}

// requestAct shoue never be zero
func ActingMatchByBits(requestAct, policyAct int) bool {
	return (requestAct & policyAct) != 0
}

func ActingMatchByName(requestAct, policyAct string) bool {
	if policyAct == ActAny {
		return true
	}
	return requestAct == policyAct
}

func DomainMatchFunc(args ...any) (any, error) {
	if len(args) != 2 {
		return nil, errors.New("domain match func only accept two arguments")
	}
	arg1IsNum, arg2IsNum := false, false
	// args[0] is r.dom
	name1, err := strconv.ParseUint(args[0].(string), 10, 64)
	if err == nil {
		arg1IsNum = true
	}
	// args[1] is p.dom
	name2, err := strconv.ParseUint(args[1].(string), 10, 64)
	if err == nil {
		arg2IsNum = true
	}
	if arg1IsNum && arg2IsNum {
		return name1 == name2, nil
	}
	return DomainMatch(args[0].(string), args[1].(string)), nil
}

func DomainMatch(dom1, dom2 string) bool {
	// log.Info("DomainMatch", zap.String("dom1", dom1), zap.String("dom2", dom2), zap.Stack("stack"))

	if dom1 == strconv.FormatUint(math.MaxUint, 10) || dom2 == strconv.FormatUint(math.MaxUint, 10) {
		return true
	}
	if dom2 == "*" {
		return true
	}
	return dom1 == dom2
}

type Resource string

func User() Resource {
	return "user"
}

// TODO: rename
func TenantResource() Resource {
	return "tenant"
}

func Cluster() Resource {
	return "cluster"
}

func Operation() Resource {
	return "operation"
}

func FaultDrillFeature() Resource {
	return "fault-drill-feature"
}

func FaultDrillExp() Resource {
	return "fault-drill-exp"
}

type Tenant uint

func (t Tenant) toString() string {
	return fmt.Sprintf("%d", t)
}

// TODO: rename
type PolicyOrg struct {
	Role     string   `toml:"role"`
	Tenant   Tenant   `toml:"tenant"`
	Resource Resource `toml:"resource"`
	Acting   Acting   `toml:"acting"`
}

func (p PolicyOrg) toArray() []string {
	return []string{
		p.Role,
		p.Tenant.toString(),
		string(p.Resource),
		p.Acting.ToString(),
	}
}

type GroupPolicy struct {
	Item   string `toml:"crole"`
	Role   string `toml:"prole"`
	Tenant Tenant
}

func (g GroupPolicy) toArray() []string {
	return []string{
		g.Item,
		g.Role,
		g.Tenant.toString(),
	}
}

var (
	defaultOrgLevelPolicy      []PolicyOrg
	defaultOrgLevelRoles       []string
	defaultOrgLevelGroupPolicy []GroupPolicy
	// TODO: rename, useless
	// internalUser map[string]Acting
)

type InternalRule struct {
	Resource string `toml:"resource"`
	Acting   Acting `toml:"acting"`
}

func loadDefaultPolicy() error {
	defaultOrgLevelRoles = []string{RoleAdmin, RoleEditor, RoleViewer}

	defaultOrgLevelPolicy = []PolicyOrg{
		{Role: RoleAdmin, Resource: "user", Acting: ActAny},
		{Role: RoleAdmin, Resource: "tenant", Acting: ActAny},
		{Role: RoleEditor, Resource: "cluster", Acting: ActAnalyze},
		{Role: RoleEditor, Resource: "cluster", Acting: ActWrite},
		{Role: RoleViewer, Resource: "cluster", Acting: ActRead},
	}

	defaultOrgLevelGroupPolicy = []GroupPolicy{
		{Item: RoleEditor, Role: RoleViewer},
		{Item: RoleAdmin, Role: RoleEditor},
	}
	//
	//ps := struct {
	//	Policy         []PolicyOrg    `toml:"policy"`
	//	GroupPolicy    []GroupPolicy  `toml:"rbac"`
	//	InternalPolicy []InternalRule `toml:"internal"`
	//}{}
	//data, err := os.ReadFile(path)
	//if err != nil {
	//	log.Error("read file failed", zap.Error(err))
	//	return err
	//}
	//if err := toml.Unmarshal(data, &ps); err != nil {
	//	log.Error("toml unmarshal failed", zap.Error(err))
	//	return err
	//}
	//
	//roles := make([]string, 0)
	//tmp := make(map[string]struct{})
	//
	//stream.OfSlice(ps.Policy).Filter(func(po PolicyOrg) bool {
	//	_, ok := tmp[po.Role]
	//	if !ok {
	//		tmp[po.Role] = struct{}{}
	//	}
	//
	//	return !ok
	//}).Map(func(po PolicyOrg) string {
	//	return po.Role
	//}).CollectEmptySlice(&roles)
	//
	//defaultOrgLevelPolicy = ps.Policy
	//defaultOrgLevelGroupPolicy = ps.GroupPolicy
	//defaultOrgLevelRoles = roles

	//internalUser = make(map[string]Acting)
	//for _, policy := range ps.InternalPolicy {
	//	internalUser[policy.Resource] = policy.Acting
	//}
	return nil
}

func formatRole(roleType, id string) string {
	return fmt.Sprintf("%s-%s", roleType, id)
}

func extractRole(role string) []string {
	return strings.Split(role, "-")
}

func getUserSubject(userID uint) string {
	return fmt.Sprintf("%s%d", userSubjectPrefix, userID)
}

func getRoleSubject(roleID uint) string {
	return fmt.Sprintf("%s%d", roleSubjectPrefix, roleID)
}

const (
	userSubjectPrefix = "user/"
	roleSubjectPrefix = "role/"
)
