package main

import (
	"context"
	"fmt"
	"log"

	"code.justin.tv/beefcake/server/internal/config"
	"code.justin.tv/beefcake/server/internal/role"
	"code.justin.tv/beefcake/server/internal/stringutil"
	"code.justin.tv/beefcake/server/internal/user"
	"code.justin.tv/beefcake/server/internal/user/useriface"
	"code.justin.tv/beefcake/server/rpc/beefcake"
	"github.com/aws/aws-lambda-go/events"
)

type handler struct {
	Config *config.Config
	Users  useriface.UsersAPI
}

func (h *handler) Handle(ctx context.Context, event events.DynamoDBEvent) error {
	for _, record := range event.Records {
		if err := h.updateRemovedUserMembers(ctx, record); err != nil {
			return err
		}
		if err := h.ensureNewPermissions(ctx, record); err != nil {
			return err
		}
		if err := h.updateRemovedPermissions(ctx, record); err != nil {
			return err
		}
	}
	return nil
}

func (h *handler) updateRemovedUserMembers(ctx context.Context, record events.DynamoDBEventRecord) error {
	users := stringutil.NewSet(h.userMembers(record.Change.OldImage)...)
	if len(users) == 0 {
		return nil
	}

	for _, user := range h.userMembers(record.Change.NewImage) {
		users.Remove(user)
	}

	perms := make(user.Permissions)
	for key, value := range h.permissions(record.Change.OldImage) {
		perms.Add(key, value.Binary())
	}

	roleID := h.roleID(record.Change.OldImage)
	log.Printf("Removing role<%v> and permissions<%v> from users<%v>.", roleID, perms, users)
	for userID := range users {
		if err := h.Users.RemoveRoleMembership(ctx, userID, roleID, perms); err != nil {
			return err
		}
	}

	return nil
}

func (h *handler) ensureNewPermissions(ctx context.Context, record events.DynamoDBEventRecord) error {
	perms := make(user.Permissions)
	for key, value := range h.permissions(record.Change.NewImage) {
		perms.Add(key, value.Binary())
	}

	for _, userID := range h.userMembers(record.Change.NewImage) {
		roleMembership, err := h.roleMembership(record.Change.NewImage, userID)
		if err != nil {
			return err
		}

		log.Printf("Adding role membership<%v> with perms<%v> to user<%v>.", roleMembership, perms, userID)
		if err := h.Users.AddRoleMembership(ctx, userID, roleMembership, perms); err != nil {
			return err
		}
	}

	return nil
}

func (h *handler) updateRemovedPermissions(ctx context.Context, record events.DynamoDBEventRecord) error {
	perms := make(user.Permissions)
	for key, value := range h.permissions(record.Change.OldImage) {
		perms.Add(key, value.Binary())
	}

	for key := range h.permissions(record.Change.NewImage) {
		perms.Remove(key)
	}

	userMembers := h.userMembers(record.Change.NewImage)
	log.Printf("Removing permissions<%v> from users<%v>.", perms, userMembers)
	for _, userID := range userMembers {
		if err := h.Users.RemovePermissions(ctx, userID, perms); err != nil {
			return err
		}
	}

	return nil
}

func (h *handler) roleUserMembership(av map[string]events.DynamoDBAttributeValue, userID string) (*beefcake.Role_UserMembership, error) {
	raw, ok := av[role.UserMembershipsAttribute]
	if !ok {
		return nil, fmt.Errorf("Attribute %s does not exist", role.UserMembershipsAttribute)
	}

	membership, ok := raw.Map()[userID]
	if !ok {
		return nil, fmt.Errorf("membership does not exist for %s", userID)
	}

	var um role.UserMembership
	if err := um.UnmarshalBytes(membership.Binary()); err != nil {
		return nil, err
	}

	bm := beefcake.Role_UserMembership(um)
	return &bm, nil
}

func (h *handler) roleMembership(av map[string]events.DynamoDBAttributeValue, userID string) (*beefcake.User_RoleMembership, error) {
	roleUserMembership, err := h.roleUserMembership(av, userID)
	if err != nil {
		return nil, err
	}

	return &beefcake.User_RoleMembership{
		Id:                   h.roleID(av),
		Name:                 h.roleName(av),
		MembershipExpiration: roleUserMembership.Expiration,
	}, nil
}

func (h *handler) roleID(av map[string]events.DynamoDBAttributeValue) string {
	id, ok := av[role.IDAttribute]
	if !ok {
		return ""
	}
	return id.String()
}

func (h *handler) roleName(av map[string]events.DynamoDBAttributeValue) string {
	name, ok := av[role.NameAttribute]
	if !ok {
		return ""
	}
	return name.String()
}

func (h *handler) userMembers(av map[string]events.DynamoDBAttributeValue) []string {
	raw, ok := av[role.UserMembershipsAttribute]
	if !ok {
		return nil
	}

	members := make([]string, 0, len(raw.Map()))
	for key := range raw.Map() {
		members = append(members, key)
	}
	return members
}

func (h *handler) permissions(av map[string]events.DynamoDBAttributeValue) map[string]events.DynamoDBAttributeValue {
	members, ok := av[role.PermissionsAttribute]
	if !ok {
		return nil
	}
	return members.Map()
}
