package beefcakeserver

import (
	"context"
	"encoding/json"
	"net/http"
	"time"

	"code.justin.tv/beefcake/server/internal/client"
	"code.justin.tv/beefcake/server/internal/config"
	"code.justin.tv/beefcake/server/internal/legacyperm"
	"code.justin.tv/beefcake/server/internal/legacyperm/legacypermiface"
	"code.justin.tv/beefcake/server/internal/optional"
	"code.justin.tv/beefcake/server/internal/role"
	"code.justin.tv/beefcake/server/internal/role/roleiface"
	"code.justin.tv/beefcake/server/internal/user/useriface"
	"code.justin.tv/beefcake/server/rpc/beefcake"
	"github.com/twitchtv/twirp"
)

// New returns a new handler that serves beefcake
func New(clientLoader *client.Loader) (http.Handler, error) {
	s := &Server{
		Config:            clientLoader.Config(),
		LegacyPermissions: clientLoader.LegacyPermissions(),
		Roles:             clientLoader.Roles(),
		Users:             clientLoader.Users(),
	}

	s2s, err := clientLoader.S2SCalleeClient()
	if err != nil {
		return nil, err
	}

	authed := s2s.CapabilitiesAuthorizerMiddleware
	mux := http.NewServeMux()
	mux.Handle("/health", http.HandlerFunc(s.HealthHandler))
	mux.Handle(beefcake.BeefcakePathPrefix, authed(beefcake.NewBeefcakeServer(s, nil)))
	return mux, nil
}

// Server serves beefcake
type Server struct {
	Config            *config.Config
	LegacyPermissions legacypermiface.LegacyPermissionsAPI
	Roles             roleiface.RolesAPI
	Users             useriface.UsersAPI
}

// HealthHandler serves the health check status.
func (s *Server) HealthHandler(w http.ResponseWriter, r *http.Request) {
	if err := json.NewEncoder(w).Encode(struct{}{}); err != nil {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
}

// CreateRole creates a role
func (s *Server) CreateRole(ctx context.Context, input *beefcake.CreateRoleRequest) (*beefcake.CreateRoleResponse, error) {
	res, err := s.Roles.Create(ctx, input.GetName())
	if err != nil {
		switch err.(type) {
		case role.ErrMissingRoleParameters:
			return nil, twirp.InvalidArgumentError("Name", err.Error())
		}
		return nil, err
	}
	return &beefcake.CreateRoleResponse{
		Id:   res.ID,
		Name: res.Name,
	}, nil
}

// AddRolePermission adds a permission for a role
func (s *Server) AddRolePermission(ctx context.Context, input *beefcake.AddRolePermissionRequest) (*beefcake.AddRolePermissionResponse, error) {
	if err := s.Roles.AddPermission(ctx, input.GetRoleId(), input.GetPermission()); err != nil {
		switch err {
		case role.ErrCannotAddLegacyPermission:
			return nil, twirp.InvalidArgumentError("Permission", "cannot be a legacy permission")
		}
		return nil, err
	}
	return &beefcake.AddRolePermissionResponse{}, nil
}

// AddLegacyPermissionToRole adds a permission for a role
func (s *Server) AddLegacyPermissionToRole(ctx context.Context, input *beefcake.AddLegacyPermissionToRoleRequest) (*beefcake.AddLegacyPermissionToRoleResponse, error) {
	if err := s.Roles.AddLegacyPermission(ctx, input.GetRoleId(), input.GetLegacyPermissionId()); err != nil {
		return nil, err
	}
	return &beefcake.AddLegacyPermissionToRoleResponse{}, nil
}

// RemoveLegacyPermissionFromRole adds a permission for a role
func (s *Server) RemoveLegacyPermissionFromRole(ctx context.Context, input *beefcake.RemoveLegacyPermissionFromRoleRequest) (*beefcake.RemoveLegacyPermissionFromRoleResponse, error) {
	if err := s.Roles.RemoveLegacyPermission(ctx, input.GetRoleId(), input.GetLegacyPermissionId()); err != nil {
		return nil, err
	}
	return &beefcake.RemoveLegacyPermissionFromRoleResponse{}, nil
}

// GetRoles gets all roles
func (s *Server) GetRoles(ctx context.Context, _ *beefcake.GetRolesRequest) (*beefcake.GetRolesResponse, error) {
	allRoles, err := s.Roles.All(ctx)
	if err != nil {
		return nil, err
	}

	roles := make([]*beefcake.GetRolesResponse_Role, 0, len(allRoles))
	for _, role := range allRoles {
		roles = append(roles, &beefcake.GetRolesResponse_Role{
			Id:   role.ID,
			Name: role.Name,
		})
	}

	return &beefcake.GetRolesResponse{Roles: roles}, nil
}

// GetRole gets a role
func (s *Server) GetRole(ctx context.Context, input *beefcake.GetRoleRequest) (*beefcake.Role, error) {
	role, err := s.Roles.Get(ctx, input.GetId())
	if err != nil {
		return nil, err
	}

	return &beefcake.Role{
		Id:              role.ID,
		Name:            role.Name,
		Permissions:     []*beefcake.AttachedPermission(role.Permissions),
		UserMemberships: role.UserMemberships,
	}, nil
}

// UpdateRole updates a role
func (s *Server) UpdateRole(ctx context.Context, input *beefcake.UpdateRoleRequest) (*beefcake.UpdateRoleResponse, error) {
	if err := s.Roles.Update(ctx, input.GetId(), role.UpdateOptions{
		Name:                optional.StringFromWrapper(input.GetName()),
		LegacyPermissionIDs: optional.StringSliceFromWrapper(input.GetLegacyPermissionIds()),
	}); err != nil {
		switch err.(type) {
		case role.ErrRoleDoesNotExist:
			return nil, twirp.InvalidArgumentError("Id", "role does not exist")
		}
		return nil, err
	}
	return &beefcake.UpdateRoleResponse{}, nil
}

// RemoveRolePermission removes a permission for a role
func (s *Server) RemoveRolePermission(ctx context.Context, input *beefcake.RemoveRolePermissionRequest) (*beefcake.RemoveRolePermissionResponse, error) {
	if err := s.Roles.RemovePermission(ctx, input.GetRoleId(), input.GetRolePermissionId()); err != nil {
		return nil, err
	}
	return &beefcake.RemoveRolePermissionResponse{}, nil
}

// AddUserToRole adds a user to a role
func (s *Server) AddUserToRole(ctx context.Context, input *beefcake.AddUserToRoleRequest) (*beefcake.AddUserToRoleResponse, error) {
	if err := s.Roles.AddUserMembership(ctx, input.GetRoleId(), &beefcake.Role_UserMembership{
		UserId:     input.GetUserId(),
		Expiration: input.GetMembershipExpiration(),
	}); err != nil {
		return nil, err
	}
	return &beefcake.AddUserToRoleResponse{}, nil
}

// RemoveUserFromRole removes a user from a role
func (s *Server) RemoveUserFromRole(ctx context.Context, input *beefcake.RemoveUserFromRoleRequest) (*beefcake.RemoveUserFromRoleResponse, error) {
	if err := s.Roles.RemoveUserMembership(ctx, input.GetRoleId(), input.GetUserId()); err != nil {
		return nil, err
	}
	return &beefcake.RemoveUserFromRoleResponse{}, nil
}

// DeleteRole removes a role
func (s *Server) DeleteRole(ctx context.Context, input *beefcake.DeleteRoleRequest) (*beefcake.DeleteRoleResponse, error) {
	if err := s.Roles.Delete(ctx, input.GetId()); err != nil {
		return nil, err
	}
	return &beefcake.DeleteRoleResponse{}, nil
}

// OverrideRole overides all role settings regardless of previous settings
func (s *Server) OverrideRole(ctx context.Context, input *beefcake.OverrideRoleRequest) (*beefcake.OverrideRoleResponse, error) {
	if err := s.Roles.Override(
		ctx,
		input.GetId(),
		input.GetName(),
		input.GetPermissions(),
		input.GetUserMemberships(),
	); err != nil {
		return nil, err
	}
	return &beefcake.OverrideRoleResponse{}, nil
}

// GetUser retrieves permissions for a user
func (s *Server) GetUser(ctx context.Context, input *beefcake.GetUserRequest) (*beefcake.User, error) {
	user, err := s.Users.Get(ctx, input.GetId())
	if err != nil {
		return nil, err
	}

	if err := s.Users.UpdateAccessTime(ctx, input.GetId(), time.Now()); err != nil {
		return nil, err
	}

	return &beefcake.User{
		Id:              user.ID,
		RoleMemberships: []*beefcake.User_RoleMembership(user.RoleMemberships),
		Permissions:     []*beefcake.AttachedPermission(user.Permissions),
	}, nil
}

// GetLegacyPermissions gets all legacy permissions
func (s *Server) GetLegacyPermissions(ctx context.Context, input *beefcake.GetLegacyPermissionsRequest) (*beefcake.GetLegacyPermissionsResponse, error) {
	all, err := s.LegacyPermissions.All(ctx)
	if err != nil {
		return nil, err
	}

	return &beefcake.GetLegacyPermissionsResponse{
		LegacyPermissions: all,
	}, nil
}

// CreateLegacyPermission creates a legacy permission
func (s *Server) CreateLegacyPermission(ctx context.Context, input *beefcake.CreateLegacyPermissionRequest) (*beefcake.CreateLegacyPermissionResponse, error) {
	err := s.LegacyPermissions.Create(ctx, input.GetId(), input.GetName(), input.GetDescription())
	if err != nil {
		return nil, err
	}

	return &beefcake.CreateLegacyPermissionResponse{}, nil
}

// GetLegacyPermission gets a legacy permission
func (s *Server) GetLegacyPermission(ctx context.Context, input *beefcake.GetLegacyPermissionRequest) (*beefcake.LegacyPermission, error) {
	lp, err := s.LegacyPermissions.Get(ctx, input.GetId())
	if err != nil {
		return nil, err
	}

	return &beefcake.LegacyPermission{
		Id:          lp.ID,
		Name:        lp.Name,
		Description: lp.Description,
		Roles:       []*beefcake.LegacyPermission_Role(lp.Roles),
	}, nil
}

// UpdateLegacyPermission gets a legacy permission
func (s *Server) UpdateLegacyPermission(ctx context.Context, input *beefcake.UpdateLegacyPermissionRequest) (*beefcake.UpdateLegacyPermissionResponse, error) {
	if err := s.LegacyPermissions.Update(ctx, input.GetId(), legacyperm.UpdateOptions{
		Name:        optional.StringFromWrapper(input.GetName()),
		Description: optional.StringFromWrapper(input.GetDescription()),
	}); err != nil {
		switch err.(type) {
		case legacyperm.ErrLegacyPermissionDoesNotExist:
			return nil, twirp.InvalidArgumentError("Id", "legacy permission does not exist")
		}
		return nil, err
	}

	return &beefcake.UpdateLegacyPermissionResponse{}, nil
}

// DeleteLegacyPermission deletes a legacy permission
func (s *Server) DeleteLegacyPermission(ctx context.Context, input *beefcake.DeleteLegacyPermissionRequest) (*beefcake.DeleteLegacyPermissionResponse, error) {
	err := s.LegacyPermissions.Delete(ctx, input.GetId())
	if err != nil {
		return nil, err
	}

	return &beefcake.DeleteLegacyPermissionResponse{}, nil
}
