/*
 * 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.models.session;

import com.fasterxml.jackson.annotation.JsonProperty;
import org.keycloak.models.AuthenticatedClientSessionModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserSessionModel;
import org.keycloak.util.JsonSerialization;

import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

/**
 * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
 */
public class PersistentAuthenticatedClientSessionAdapter implements AuthenticatedClientSessionModel {

    private final PersistentClientSessionModel model;
    private final RealmModel realm;
    private final ClientModel client;
    private UserSessionModel userSession;

    private PersistentClientSessionData data;

    public PersistentAuthenticatedClientSessionAdapter(AuthenticatedClientSessionModel clientSession) {
        data = new PersistentClientSessionData();
        data.setAction(clientSession.getAction());
        data.setAuthMethod(clientSession.getProtocol());
        data.setNotes(clientSession.getNotes());
        data.setProtocolMappers(clientSession.getProtocolMappers());
        data.setRedirectUri(clientSession.getRedirectUri());
        data.setRoles(clientSession.getRoles());

        model = new PersistentClientSessionModel();
        model.setClientId(clientSession.getClient().getId());
        model.setUserId(clientSession.getUserSession().getUser().getId());
        model.setUserSessionId(clientSession.getUserSession().getId());
        model.setTimestamp(clientSession.getTimestamp());

        realm = clientSession.getRealm();
        client = clientSession.getClient();
        userSession = clientSession.getUserSession();
    }

    public PersistentAuthenticatedClientSessionAdapter(PersistentClientSessionModel model, RealmModel realm, ClientModel client, UserSessionModel userSession) {
        this.model = model;
        this.realm = realm;
        this.client = client;
        this.userSession = userSession;
    }

    // Lazily init data
    private PersistentClientSessionData getData() {
        if (data == null) {
            try {
                data = JsonSerialization.readValue(model.getData(), PersistentClientSessionData.class);
            } catch (IOException ioe) {
                throw new ModelException(ioe);
            }
        }

        return data;
    }

    // Write updated model with latest serialized data
    public PersistentClientSessionModel getUpdatedModel() {
        try {
            String updatedData = JsonSerialization.writeValueAsString(getData());
            this.model.setData(updatedData);
        } catch (IOException ioe) {
            throw new ModelException(ioe);
        }

        return this.model;
    }

    @Override
    public String getId() {
        return null;
    }

    @Override
    public RealmModel getRealm() {
        return realm;
    }

    @Override
    public ClientModel getClient() {
        return client;
    }

    @Override
    public UserSessionModel getUserSession() {
        return userSession;
    }

    @Override
    public void detachFromUserSession() {
        setUserSession(null);
    }

    public void setUserSession(UserSessionModel userSession) {
        this.userSession = userSession;
    }

    @Override
    public String getRedirectUri() {
        return getData().getRedirectUri();
    }

    @Override
    public void setRedirectUri(String uri) {
        getData().setRedirectUri(uri);
    }

    @Override
    public int getTimestamp() {
        return model.getTimestamp();
    }

    @Override
    public void setTimestamp(int timestamp) {
        model.setTimestamp(timestamp);
    }

    @Override
    public String getCurrentRefreshToken() {
        return null; // Information not persisted.
    }

    @Override
    public void setCurrentRefreshToken(String currentRefreshToken) {
        // Information not persisted.
    }

    @Override
    public int getCurrentRefreshTokenUseCount() {
        return 0; // Information not persisted.
    }

    @Override
    public void setCurrentRefreshTokenUseCount(int currentRefreshTokenUseCount) {
        // Information not persisted.
    }

    @Override
    public String getAction() {
        return getData().getAction();
    }

    @Override
    public void setAction(String action) {
        getData().setAction(action);
    }

    @Override
    public Set<String> getRoles() {
        return getData().getRoles();
    }

    @Override
    public void setRoles(Set<String> roles) {
        getData().setRoles(roles);
    }

    @Override
    public Set<String> getProtocolMappers() {
        return getData().getProtocolMappers();
    }

    @Override
    public void setProtocolMappers(Set<String> protocolMappers) {
        getData().setProtocolMappers(protocolMappers);
    }

    @Override
    public String getProtocol() {
        return getData().getAuthMethod();
    }

    @Override
    public void setProtocol(String method) {
        getData().setAuthMethod(method);
    }

    @Override
    public String getNote(String name) {
        PersistentClientSessionData entity = getData();
        return entity.getNotes()==null ? null : entity.getNotes().get(name);
    }

    @Override
    public void setNote(String name, String value) {
        PersistentClientSessionData entity = getData();
        if (entity.getNotes() == null) {
            entity.setNotes(new HashMap<>());
        }
        entity.getNotes().put(name, value);
    }

    @Override
    public void removeNote(String name) {
        PersistentClientSessionData entity = getData();
        if (entity.getNotes() != null) {
            entity.getNotes().remove(name);
        }
    }

    @Override
    public Map<String, String> getNotes() {
        PersistentClientSessionData entity = getData();
        if (entity.getNotes() == null || entity.getNotes().isEmpty()) return Collections.emptyMap();
        return entity.getNotes();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || !(o instanceof AuthenticatedClientSessionModel)) return false;

        AuthenticatedClientSessionModel that = (AuthenticatedClientSessionModel) o;
        return that.getId().equals(getId());
    }

    @Override
    public int hashCode() {
        return getId().hashCode();
    }

    protected static class PersistentClientSessionData {

        @JsonProperty("authMethod")
        private String authMethod;

        @JsonProperty("redirectUri")
        private String redirectUri;

        @JsonProperty("protocolMappers")
        private Set<String> protocolMappers;

        @JsonProperty("roles")
        private Set<String> roles;

        @JsonProperty("notes")
        private Map<String, String> notes;

        @JsonProperty("action")
        private String action;

        // TODO: Keeping those just for backwards compatibility. @JsonIgnoreProperties doesn't work on Wildfly - probably due to classloading issues
        @JsonProperty("userSessionNotes")
        private Map<String, String> userSessionNotes;
        @JsonProperty("executionStatus")
        private Map<String, Object> executionStatus;
        @JsonProperty("requiredActions")
        private Set<String> requiredActions;


        public String getAuthMethod() {
            return authMethod;
        }

        public void setAuthMethod(String authMethod) {
            this.authMethod = authMethod;
        }

        public String getRedirectUri() {
            return redirectUri;
        }

        public void setRedirectUri(String redirectUri) {
            this.redirectUri = redirectUri;
        }

        public Set<String> getProtocolMappers() {
            return protocolMappers;
        }

        public void setProtocolMappers(Set<String> protocolMappers) {
            this.protocolMappers = protocolMappers;
        }

        public Set<String> getRoles() {
            return roles;
        }

        public void setRoles(Set<String> roles) {
            this.roles = roles;
        }

        public Map<String, String> getNotes() {
            return notes;
        }

        public void setNotes(Map<String, String> notes) {
            this.notes = notes;
        }

        public String getAction() {
            return action;
        }

        public void setAction(String action) {
            this.action = action;
        }

        public Map<String, String> getUserSessionNotes() {
            return userSessionNotes;
        }

        public void setUserSessionNotes(Map<String, String> userSessionNotes) {
            this.userSessionNotes = userSessionNotes;
        }

        public Map<String, Object> getExecutionStatus() {
            return executionStatus;
        }

        public void setExecutionStatus(Map<String, Object> executionStatus) {
            this.executionStatus = executionStatus;
        }

        public Set<String> getRequiredActions() {
            return requiredActions;
        }

        public void setRequiredActions(Set<String> requiredActions) {
            this.requiredActions = requiredActions;
        }
    }
}
