package communities

import (
	"encoding/json"
	"sort"

	gethcommon "github.com/ethereum/go-ethereum/common"
	"github.com/status-im/status-go/protocol/protobuf"
)

type CheckPermissionsResponse struct {
	Satisfied            bool                                      `json:"satisfied"`
	Permissions          map[string]*PermissionTokenCriteriaResult `json:"permissions"`
	ValidCombinations    []*AccountChainIDsCombination             `json:"validCombinations"`
	NetworksNotSupported bool                                      `json:"networksNotSupported"`
}

type CheckPermissionToJoinResponse = CheckPermissionsResponse

type HighestRoleResponse struct {
	Role      protobuf.CommunityTokenPermission_Type `json:"type"`
	Satisfied bool                                   `json:"satisfied"`
	Criteria  []*PermissionTokenCriteriaResult       `json:"criteria"`
}

var roleOrders = map[protobuf.CommunityTokenPermission_Type]int{
	protobuf.CommunityTokenPermission_BECOME_MEMBER:             1,
	protobuf.CommunityTokenPermission_CAN_VIEW_CHANNEL:          2,
	protobuf.CommunityTokenPermission_CAN_VIEW_AND_POST_CHANNEL: 3,
	protobuf.CommunityTokenPermission_BECOME_ADMIN:              4,
	protobuf.CommunityTokenPermission_BECOME_TOKEN_MASTER:       5,
	protobuf.CommunityTokenPermission_BECOME_TOKEN_OWNER:        6,
}

type ByRoleDesc []*HighestRoleResponse

func (a ByRoleDesc) Len() int      { return len(a) }
func (a ByRoleDesc) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a ByRoleDesc) Less(i, j int) bool {
	return roleOrders[a[i].Role] > roleOrders[a[j].Role]
}

type rolesAndHighestRole struct {
	Roles       []*HighestRoleResponse
	HighestRole *HighestRoleResponse
}

func calculateRolesAndHighestRole(permissions map[string]*PermissionTokenCriteriaResult) *rolesAndHighestRole {
	item := &rolesAndHighestRole{}
	byRoleMap := make(map[protobuf.CommunityTokenPermission_Type]*HighestRoleResponse)
	for _, p := range permissions {
		if roleOrders[p.Role] == 0 {
			continue
		}
		if byRoleMap[p.Role] == nil {
			byRoleMap[p.Role] = &HighestRoleResponse{
				Role: p.Role,
			}
		}

		satisfied := true
		for _, tr := range p.TokenRequirements {
			if !tr.Satisfied {
				satisfied = false
				break
			}

		}

		if satisfied {
			byRoleMap[p.Role].Satisfied = true
			// we prepend
			byRoleMap[p.Role].Criteria = append([]*PermissionTokenCriteriaResult{p}, byRoleMap[p.Role].Criteria...)
		} else {
			// we append then
			byRoleMap[p.Role].Criteria = append(byRoleMap[p.Role].Criteria, p)
		}
	}
	if byRoleMap[protobuf.CommunityTokenPermission_BECOME_MEMBER] == nil {
		byRoleMap[protobuf.CommunityTokenPermission_BECOME_MEMBER] = &HighestRoleResponse{Satisfied: true, Role: protobuf.CommunityTokenPermission_BECOME_MEMBER}
	}
	for _, p := range byRoleMap {
		item.Roles = append(item.Roles, p)
	}

	sort.Sort(ByRoleDesc(item.Roles))
	for _, r := range item.Roles {
		if r.Satisfied {
			item.HighestRole = r
			break
		}

	}
	return item
}

func (c *CheckPermissionsResponse) MarshalJSON() ([]byte, error) {
	type CheckPermissionsTypeAlias struct {
		Satisfied            bool                                      `json:"satisfied"`
		Permissions          map[string]*PermissionTokenCriteriaResult `json:"permissions"`
		ValidCombinations    []*AccountChainIDsCombination             `json:"validCombinations"`
		Roles                []*HighestRoleResponse                    `json:"roles"`
		HighestRole          *HighestRoleResponse                      `json:"highestRole"`
		NetworksNotSupported bool                                      `json:"networksNotSupported"`
	}
	c.calculateSatisfied()
	item := &CheckPermissionsTypeAlias{
		Satisfied:            c.Satisfied,
		Permissions:          c.Permissions,
		ValidCombinations:    c.ValidCombinations,
		NetworksNotSupported: c.NetworksNotSupported,
	}
	rolesAndHighestRole := calculateRolesAndHighestRole(c.Permissions)

	item.Roles = rolesAndHighestRole.Roles
	item.HighestRole = rolesAndHighestRole.HighestRole
	return json.Marshal(item)
}

type TokenRequirementResponse struct {
	Satisfied     bool                    `json:"satisfied"`
	TokenCriteria *protobuf.TokenCriteria `json:"criteria"`
}

type PermissionTokenCriteriaResult struct {
	Role              protobuf.CommunityTokenPermission_Type `json:"roles"`
	TokenRequirements []TokenRequirementResponse             `json:"tokenRequirement"`
	Criteria          []bool                                 `json:"criteria"`
	ID                string                                 `json:"id"`
}

type AccountChainIDsCombination struct {
	Address  gethcommon.Address `json:"address"`
	ChainIDs []uint64           `json:"chainIds"`
}

func (c *CheckPermissionsResponse) calculateSatisfied() {
	if len(c.Permissions) == 0 {
		c.Satisfied = true
		return
	}

	c.Satisfied = false
	for _, p := range c.Permissions {
		satisfied := true
		for _, criteria := range p.Criteria {
			if !criteria {
				satisfied = false
				break
			}
		}
		if satisfied {
			c.Satisfied = true
			return
		}
	}
}