/*
 * Copyright 2016 Red Hat, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.keycloak.testsuite.util.cli;

import org.infinispan.AdvancedCache;
import org.infinispan.Cache;
import org.infinispan.context.Flag;
import org.keycloak.common.util.Time;
import org.keycloak.connections.infinispan.InfinispanConnectionProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.sessions.infinispan.entities.SessionEntity;
import org.keycloak.models.sessions.infinispan.entities.UserSessionEntity;

/**
 * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
 */
public abstract class AbstractOfflineCacheCommand extends AbstractCommand {

    @Override
    protected void doRunCommand(KeycloakSession session) {
        InfinispanConnectionProvider provider = session.getProvider(InfinispanConnectionProvider.class);
        Cache<String, SessionEntity> ispnCache = provider.getCache(InfinispanConnectionProvider.OFFLINE_SESSION_CACHE_NAME);
        doRunCacheCommand(session, ispnCache);
    }

    protected void printSession(String id, UserSessionEntity userSession) {
        if (userSession == null) {
            log.info("Not found session with Id: " + id);
        } else {
            log.info("Found session. ID: " + toString(userSession));
        }
    }

    protected String toString(UserSessionEntity userSession) {
        int clientSessionsSize = userSession.getAuthenticatedClientSessions()==null ? 0 : userSession.getAuthenticatedClientSessions().size();
        return "ID: " + userSession.getId() + ", realm: " + userSession.getRealm() + ", lastAccessTime: " + Time.toDate(userSession.getLastSessionRefresh()) +
                ", authenticatedClientSessions: " + clientSessionsSize;
    }

    protected abstract void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache);


    // IMPLS

    public static class PutCommand extends AbstractOfflineCacheCommand {

        @Override
        public String getName() {
            return "put";
        }

        @Override
        protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
            UserSessionEntity userSession = new UserSessionEntity();
            String id = getArg(0);

            userSession.setId(id);
            userSession.setRealm(getArg(1));

            userSession.setLastSessionRefresh(Time.currentTime());
            cache.put(id, userSession);
        }

        @Override
        public String printUsage() {
            return getName() + " <user-session-id> <realm-name>";
        }
    }


    public static class GetCommand extends AbstractOfflineCacheCommand {

        @Override
        public String getName() {
            return "get";
        }

        @Override
        protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
            String id = getArg(0);
            UserSessionEntity userSession = (UserSessionEntity) cache.get(id);
            printSession(id, userSession);
        }

        @Override
        public String printUsage() {
            return getName() + " <user-session-id>";
        }
    }

    // Just to check performance of multiple get calls. And comparing what's the change between the case when item is available locally or not.
    public static class GetMultipleCommand extends AbstractOfflineCacheCommand {

        @Override
        public String getName() {
            return "getMulti";
        }

        @Override
        protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
            String id = getArg(0);
            int count = getIntArg(1);

            long start = System.currentTimeMillis();
            for (int i=0 ; i<count ; i++) {
                UserSessionEntity userSession = (UserSessionEntity) cache.get(id);
                //printSession(id, userSession);
            }
            long took = System.currentTimeMillis() - start;
            log.infof("Took %d milliseconds", took);
        }

        @Override
        public String printUsage() {
            return getName() + " <user-session-id> <count-of-gets>";
        }
    }


    public static class RemoveCommand extends AbstractOfflineCacheCommand {

        @Override
        public String getName() {
            return "remove";
        }

        @Override
        protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
            String id = getArg(0);
            cache.remove(id);
        }

        @Override
        public String printUsage() {
            return getName() + " <user-session-id>";
        }
    }


    public static class ClearCommand extends AbstractOfflineCacheCommand {

        @Override
        public String getName() {
            return "clear";
        }

        @Override
        protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
            cache.clear();
        }
    }


    public static class SizeCommand extends AbstractOfflineCacheCommand {

        @Override
        public String getName() {
            return "size";
        }

        @Override
        protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
            log.info("Size: " + cache.size());
        }
    }


    public static class ListCommand extends AbstractOfflineCacheCommand {

        @Override
        public String getName() {
            return "list";
        }

        @Override
        protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
            for (String id : cache.keySet()) {
                SessionEntity entity = cache.get(id);
                if (!(entity instanceof UserSessionEntity)) {
                    continue;
                }
                UserSessionEntity userSession = (UserSessionEntity) cache.get(id);
                log.info("list: key=" + id + ", value=" + toString(userSession));
            }
        }
    }


    public static class GetLocalCommand extends AbstractOfflineCacheCommand {

        @Override
        public String getName() {
            return "getLocal";
        }


        @Override
        protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
            String id = getArg(0);
            cache = ((AdvancedCache) cache).withFlags(Flag.CACHE_MODE_LOCAL);
            UserSessionEntity userSession = (UserSessionEntity) cache.get(id);
            printSession(id, userSession);
        }

        @Override
        public String printUsage() {
            return getName() + " <user-session-id>";
        }
    }


    public static class SizeLocalCommand extends AbstractOfflineCacheCommand {

        @Override
        public String getName() {
            return "sizeLocal";
        }

        @Override
        protected void doRunCacheCommand(KeycloakSession session, Cache<String, SessionEntity> cache) {
            log.info("Size local: " + cache.getAdvancedCache().withFlags(Flag.CACHE_MODE_LOCAL).size());
        }
    }

}
