package perms_connectors

import (
	"encoding/json"
	"errors"
	"fmt"
	"oc-auth/conf"
	"oc-auth/infrastructure/utils"

	oclib "cloud.o-forge.io/core/oc-lib"
	"cloud.o-forge.io/core/oc-lib/tools"
)

type KetoConnector struct {
	Client string
}

func (k KetoConnector) SetClient(client string) {
	k.Client = client
}

func (k KetoConnector) namespace() string {
	return "open-cloud"
}

func (k KetoConnector) scope() string {
	return "oc-auth-realm"
}

func (f KetoConnector) permToQuery(perm Permission, permDependancies *Permission) string {
	n := "?namespace=" + f.namespace()
	if perm.Object != "" {
		n += "&object=" + perm.Object
	}
	if perm.Subject != "" {
		n += "&subject_id=" + perm.Subject
	}
	if perm.Relation != "" {
		n += "&relation=" + perm.Relation
	}
	if permDependancies != nil {
		n += "&subject_set.namespace=" + perm.Namespace()
		if permDependancies.Object != "" {
			n += "&subject_set.object=" + permDependancies.Object
		}
		if permDependancies.Subject != "" {
			n += "&subject_set.subject_id=" + permDependancies.Subject
		}
		if permDependancies.Relation != "" {
			n += "&subject_set.relation=" + permDependancies.Relation
		}
	}
	return n
}

func (k KetoConnector) Status() tools.State {
	caller := tools.NewHTTPCaller(map[tools.DataType]map[tools.METHOD]string{})
	var responseBody map[string]interface{}
	host := conf.GetConfig().PermissionConnectorReadHost
	if conf.GetConfig().Local {
		host = "localhost"
	}
	port := fmt.Sprintf("%v", conf.GetConfig().PermissionConnectorPort)
	resp, err := caller.CallGet("http://"+host+":"+port, "/health/ready")
	if err != nil {
		return tools.DEAD
	}
	err = json.Unmarshal(resp, &responseBody)
	if err != nil || responseBody["status"] != "ok" {
		return tools.DEAD
	}
	return tools.ALIVE
}

func (k KetoConnector) CheckPermission(perm Permission, permDependancies *Permission, internal bool) bool {
	if (perm.Object == k.scope() || perm.Subject == k.scope()) && !internal {
		log := oclib.GetLogger()
		log.Error().Msg("Permission denied : Ask illegal permission")
		return false
	}
	perms, err := k.GetPermission(perm.Object, perm.Relation)
	if err != nil {
		log := oclib.GetLogger()
		log.Error().Msg("CheckPermission " + err.Error())
		return false
	}
	return len(perms) > 0
}

func (k KetoConnector) deletes(object string, relation string, subject string, relation2 string) (string, int, error) {
	k.deleteRelationShip(object, relation, subject, nil)
	_, code, err := k.deleteRelationShip(subject, relation2, k.scope(), nil)
	if err != nil {
		return "", code, err
	}
	return subject, 200, nil
}

func (k KetoConnector) DeleteRole(roleID string) (string, int, error) {
	return k.deletes("", "member", roleID, "is")
}

func (k KetoConnector) DeleteGroup(groupID string) (string, int, error) {
	return k.deletes("", "groups", groupID, "groupin")
}

func (k KetoConnector) DeletePermission(permID string, relation string, internal bool) (string, int, error) {
	meth, err := utils.ExtractMethod(relation, internal)
	if err != nil {
		for _, method := range []tools.METHOD{tools.GET, tools.PUT, tools.POST, tools.DELETE} {
			k.DeletePermission("", method.String(), internal)
		}
		return "", 200, err
	}
	return k.deletes("", "groups", permID, "permits"+meth.String())
}

func (k KetoConnector) CreateRole(roleID string) (string, int, error) {
	return k.creates(roleID, "is", k.scope())
}

func (k KetoConnector) CreateGroup(groupID string) (string, int, error) {
	return k.creates(groupID, "groupin", k.scope())
}

func (k KetoConnector) CreatePermission(permID string, relation string, internal bool) (string, int, error) {
	meth, err := utils.ExtractMethod(relation, internal)
	if err != nil {
		return "", 422, err
	}
	k.BindPermission("admin", permID, "permits"+meth.String())
	return k.creates(permID, "permits"+meth.String(), k.scope())
}

func (k KetoConnector) creates(object string, relation string, subject string) (string, int, error) {
	p, code, err := k.createRelationShip(object, relation, subject, nil)
	if err != nil {
		return "", code, err
	}
	return p.Object, 200, nil
}

func (k KetoConnector) GetRole(roleID string) ([]string, error) {
	return k.gets(roleID, "is", k.scope())
}

func (k KetoConnector) GetGroup(groupID string) ([]string, error) {
	return k.gets(groupID, "groupin", k.scope())
}

func (k KetoConnector) GetRoleByUser(userID string) ([]string, error) {
	return k.gets("", "member", userID)
}

func (k KetoConnector) GetGroupByUser(userID string) ([]string, error) {
	return k.gets("", "groups", userID)
}

func (k KetoConnector) gets(object string, relation string, subject string) ([]string, error) {
	arr := []string{}
	objs, err := k.get(object, relation, subject)
	if err != nil {
		return arr, err
	}
	for _, obj := range objs {
		arr = append(arr, obj.Object)
	}
	return arr, nil
}

func (k KetoConnector) GetPermission(permID string, relation string) ([]Permission, error) {
	meth, err := utils.ExtractMethod(relation, true)
	if err != nil {
		p := []Permission{}
		for _, method := range []tools.METHOD{tools.GET, tools.PUT, tools.POST, tools.DELETE,
			tools.STRICT_INTERNAL_DELETE, tools.STRICT_INTERNAL_GET, tools.STRICT_INTERNAL_POST, tools.STRICT_INTERNAL_PUT} {
			perms, err := k.get(permID, "permits"+method.String(), k.scope())
			if err == nil && len(perms) > 0 {
				p = append(p, perms...)
			}
		}
		return p, nil
	}
	return k.get(permID, "permits"+meth.String(), k.scope())
}

func (k KetoConnector) GetPermissionByRole(roleID string) ([]Permission, error) {
	p := []Permission{}
	for _, method := range []tools.METHOD{tools.GET, tools.PUT, tools.POST, tools.DELETE,
		tools.STRICT_INTERNAL_DELETE, tools.STRICT_INTERNAL_GET, tools.STRICT_INTERNAL_POST, tools.STRICT_INTERNAL_PUT} {
		perms, err := k.get(roleID, "permits"+method.String(), "")
		if err == nil && len(perms) > 0 {
			p = append(p, perms...)
		}
	}
	return p, nil
}
func (k KetoConnector) GetPermissionByUser(userID string, internal bool) ([]Permission, error) {
	roles, err := k.get("", "member", userID)
	fmt.Println("ROLES", roles, err)
	if err != nil {
		return nil, err
	}
	p := []Permission{}
	meths := []tools.METHOD{tools.GET, tools.PUT, tools.POST, tools.DELETE}
	if internal {
		meths = append(meths, []tools.METHOD{tools.STRICT_INTERNAL_DELETE, tools.STRICT_INTERNAL_GET, tools.STRICT_INTERNAL_POST, tools.STRICT_INTERNAL_PUT}...)
	}
	for _, role := range roles {
		for _, method := range meths {
			perms, err := k.get(role.Object, "permits"+method.String(), "")
			if err == nil && len(perms) > 0 {
				p = append(p, perms...)
			}
		}
	}
	return p, nil
}

func (k KetoConnector) get(object string, relation string, subject string) ([]Permission, error) {
	t := []Permission{}
	caller := tools.NewHTTPCaller(map[tools.DataType]map[tools.METHOD]string{})
	host := conf.GetConfig().PermissionConnectorReadHost
	if conf.GetConfig().Local {
		host = "localhost"
	}
	port := fmt.Sprintf("%v", conf.GetConfig().PermissionConnectorPort)
	resp, err := caller.CallGet("http://"+host+":"+port, "/relation-tuples"+k.permToQuery(
		Permission{Object: object, Relation: relation, Subject: subject}, nil))
	if err != nil {
		return t, err
	}
	var data map[string]interface{}
	err = json.Unmarshal(resp, &data)
	if err != nil {
		return t, err
	}
	if data["relation_tuples"] != nil {
		for _, v := range data["relation_tuples"].([]interface{}) {
			t = append(t, Permission{
				Relation: v.(map[string]interface{})["relation"].(string),
				Subject:  v.(map[string]interface{})["subject_id"].(string),
				Object:   v.(map[string]interface{})["object"].(string),
			})
		}
	}
	return t, nil
}

func (k KetoConnector) binds(object string, relation string, subject string) (string, int, error) {
	_, code, err := k.createRelationShip(object, relation, subject, nil)
	if err != nil {
		return object, code, err
	}
	return object, 200, nil
}

func (k KetoConnector) BindRole(userID string, roleID string) (string, int, error) {
	fmt.Println("BIND ROLE", userID, roleID)
	return k.binds(userID, "member", roleID)
}

func (k KetoConnector) BindGroup(userID string, groupID string) (string, int, error) {
	return k.binds(userID, "groups", groupID)
}

func (k KetoConnector) BindPermission(roleID string, permID string, relation string) (*Permission, int, error) {
	perms, err := k.GetPermission(permID, relation)
	if err != nil || len(perms) != 1 {
		count := 0
		for _, p := range perms {
			if p.Relation == relation {
				count++
			}
		}
		if count == 0 {
			return nil, 404, errors.New("Permission not found")
		} else if count > 1 {
			return nil, 409, errors.New("Multiple permission found")
		}
	}
	_, code, err := k.createRelationShip(roleID, relation, permID, nil)
	if err != nil {
		return nil, code, err
	}
	return &Permission{
		Object:   roleID,
		Relation: relation,
		Subject:  permID,
	}, 200, nil
}

func (k KetoConnector) unbinds(subject string, relation string, object string) (string, int, error) {
	_, code, err := k.deleteRelationShip(object, relation, subject, nil)
	if err != nil {
		return object, code, err
	}
	return object, 200, nil
}

func (k KetoConnector) UnBindRole(userID string, roleID string) (string, int, error) {
	return k.unbinds(userID, "member", roleID)
}

func (k KetoConnector) UnBindGroup(userID string, groupID string) (string, int, error) {
	return k.unbinds(userID, "groups", groupID)
}

func (k KetoConnector) UnBindPermission(roleID string, permID string, relation string) (*Permission, int, error) {
	meth, err := utils.ExtractMethod(relation, false)
	if err != nil {
		return nil, 422, err
	}
	perms, err := k.GetPermission(permID, meth.String())
	if err != nil || len(perms) != 1 {
		count := 0
		for _, p := range perms {
			if p.Relation == relation {
				count++
			}
		}
		if count == 0 {
			return nil, 404, errors.New("Permission not found")
		} else if count > 1 {
			return nil, 409, errors.New("Multiple permission found")
		}
	}
	_, code, err := k.deleteRelationShip(roleID, perms[0].Relation, permID, nil)
	if err != nil {
		return nil, code, err
	}
	return &Permission{
		Object:   roleID,
		Relation: perms[0].Relation,
		Subject:  permID,
	}, 200, nil
}
func (k KetoConnector) createRelationShip(object string, relation string, subject string, subPerm *Permission) (*Permission, int, error) {
	exist, err := k.get(object, relation, subject)
	if err == nil && len(exist) > 0 {
		return nil, 409, errors.New("Relation already exist")
	}
	caller := tools.NewHTTPCaller(map[tools.DataType]map[tools.METHOD]string{})
	body := map[string]interface{}{"namespace": k.namespace(), "object": object, "relation": relation, "subject_id": subject}

	if subPerm != nil {
		s, code, err := k.createRelationShip(subPerm.Object, subPerm.Relation, subPerm.Subject, nil)
		if err != nil {
			return nil, code, err
		}
		body["subject_set"] = map[string]interface{}{"namespace": k.namespace(), "object": s.Object, "relation": s.Relation, "subject_id": s.Subject}
	}
	host := conf.GetConfig().PermissionConnectorWriteHost
	if conf.GetConfig().Local {
		host = "localhost"
	}
	port := fmt.Sprintf("%v", conf.GetConfig().PermissionConnectorAdminPort)
	b, err := caller.CallPut("http://"+host+":"+port, "/relation-tuples", body)
	if err != nil {
		log := oclib.GetLogger()
		log.Error().Msg("createRelationShip" + err.Error())
		return nil, 500, err
	}
	var data map[string]interface{}
	err = json.Unmarshal(b, &data)
	if err != nil {
		fmt.Println(string(b), err)
		log := oclib.GetLogger()
		log.Error().Msg("createRelationShip2" + err.Error())
		return nil, 500, err
	}
	perm := &Permission{
		Object:   data["object"].(string),
		Relation: data["relation"].(string),
		Subject:  data["subject_id"].(string),
	}
	if data["subject_set"] != nil {
		sub := data["subject_set"].(map[string]interface{})
		perm.SubPermission = &Permission{
			Object:   sub["object"].(string),
			Relation: sub["relation"].(string),
			Subject:  sub["subject_id"].(string),
		}
	}
	return perm, 200, nil
}

func (k KetoConnector) deleteRelationShip(object string, relation string, subject string, subPerm *Permission) (*Permission, int, error) {
	exist, err := k.get(object, relation, subject)
	if err == nil && len(exist) == 0 {
		return nil, 409, errors.New("Relation does not exist")
	}
	caller := tools.NewHTTPCaller(map[tools.DataType]map[tools.METHOD]string{})
	n := k.permToQuery(Permission{Object: object, Relation: relation, Subject: subject}, subPerm)
	host := conf.GetConfig().PermissionConnectorWriteHost
	if conf.GetConfig().Local {
		host = "localhost"
	}
	port := fmt.Sprintf("%v", conf.GetConfig().PermissionConnectorAdminPort)
	b, err := caller.CallDelete("http://"+host+":"+port, "/relation-tuples"+n)
	if err != nil {
		log := oclib.GetLogger()
		log.Error().Msg("deleteRelationShip " + err.Error())
		return nil, 500, err
	}
	var data map[string]interface{}
	err = json.Unmarshal(b, &data)
	if err == nil && data["code"].(int) > 300 {
		return nil, data["code"].(int), errors.New("Error while deleting relation")
	}
	return &Permission{
		Object:        object,
		Relation:      relation,
		Subject:       subject,
		SubPermission: subPerm,
	}, 200, nil
}
