package org.keycloak.models.hybrid;

import org.keycloak.models.ApplicationModel;
import org.keycloak.models.AuthenticationProviderModel;
import org.keycloak.models.ClientModel;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UsernameLoginFailureModel;
import org.keycloak.models.realms.Application;
import org.keycloak.models.realms.Client;
import org.keycloak.models.realms.OAuthClient;
import org.keycloak.models.realms.Realm;
import org.keycloak.models.users.Credentials;
import org.keycloak.models.users.Feature;
import org.keycloak.models.users.User;
import org.keycloak.models.utils.KeycloakModelUtils;
import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
import org.keycloak.models.utils.TimeBasedOTP;

import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
 */
public class RealmAdapter implements RealmModel {

    private HybridModelProvider provider;
    private Realm realm;

    RealmAdapter(HybridModelProvider provider, Realm realm) {
        this.provider = provider;
        this.realm = realm;
    }

    Realm getRealm() {
        return realm;
    }

    @Override
    public String getId() {
        return realm.getId();
    }

    @Override
    public String getName() {
        return realm.getName();
    }

    @Override
    public void setName(String name) {
        realm.setName(name);
    }

    @Override
    public boolean isEnabled() {
        return realm.isEnabled();
    }

    @Override
    public void setEnabled(boolean enabled) {
        realm.setEnabled(enabled);
    }

    @Override
    public boolean isSslNotRequired() {
        return realm.isSslNotRequired();
    }

    @Override
    public void setSslNotRequired(boolean sslNotRequired) {
        realm.setSslNotRequired(sslNotRequired);
    }

    @Override
    public boolean isRegistrationAllowed() {
        return realm.isRegistrationAllowed();
    }

    @Override
    public void setRegistrationAllowed(boolean registrationAllowed) {
        realm.setRegistrationAllowed(registrationAllowed);
    }

    @Override
    public boolean isPasswordCredentialGrantAllowed() {
        return realm.isPasswordCredentialGrantAllowed();
    }

    @Override
    public void setPasswordCredentialGrantAllowed(boolean passwordCredentialGrantAllowed) {
        realm.setPasswordCredentialGrantAllowed(passwordCredentialGrantAllowed);
    }

    @Override
    public boolean isRememberMe() {
        return realm.isRememberMe();
    }

    @Override
    public void setRememberMe(boolean rememberMe) {
        realm.setRememberMe(rememberMe);
    }

    @Override
    public boolean isBruteForceProtected() {
        return realm.isBruteForceProtected();
    }

    @Override
    public void setBruteForceProtected(boolean value) {
        realm.setBruteForceProtected(value);
    }

    @Override
    public int getMaxFailureWaitSeconds() {
        return realm.getMaxFailureWaitSeconds();
    }

    @Override
    public void setMaxFailureWaitSeconds(int val) {
        realm.setMaxFailureWaitSeconds(val);
    }

    @Override
    public int getWaitIncrementSeconds() {
        return realm.getWaitIncrementSeconds();
    }

    @Override
    public void setWaitIncrementSeconds(int val) {
        realm.setWaitIncrementSeconds(val);
    }

    @Override
    public int getMinimumQuickLoginWaitSeconds() {
        return realm.getMinimumQuickLoginWaitSeconds();
    }

    @Override
    public void setMinimumQuickLoginWaitSeconds(int val) {
        realm.setMinimumQuickLoginWaitSeconds(val);
    }

    @Override
    public long getQuickLoginCheckMilliSeconds() {
        return realm.getQuickLoginCheckMilliSeconds();
    }

    @Override
    public void setQuickLoginCheckMilliSeconds(long val) {
        realm.setQuickLoginCheckMilliSeconds(val);
    }

    @Override
    public int getMaxDeltaTimeSeconds() {
        return realm.getMaxDeltaTimeSeconds();
    }

    @Override
    public void setMaxDeltaTimeSeconds(int val) {
        realm.setMaxDeltaTimeSeconds(val);
    }

    @Override
    public int getFailureFactor() {
        return realm.getFailureFactor();
    }

    @Override
    public void setFailureFactor(int failureFactor) {
        realm.setFailureFactor(failureFactor);
    }

    @Override
    public boolean isVerifyEmail() {
        return realm.isVerifyEmail();
    }

    @Override
    public void setVerifyEmail(boolean verifyEmail) {
        realm.setVerifyEmail(verifyEmail);
    }

    @Override
    public boolean isResetPasswordAllowed() {
        return realm.isResetPasswordAllowed();
    }

    @Override
    public void setResetPasswordAllowed(boolean resetPasswordAllowed) {
        realm.setResetPasswordAllowed(resetPasswordAllowed);
    }

    @Override
    public int getSsoSessionIdleTimeout() {
        return realm.getSsoSessionIdleTimeout();
    }

    @Override
    public void setSsoSessionIdleTimeout(int seconds) {
        realm.setSsoSessionIdleTimeout(seconds);
    }

    @Override
    public int getSsoSessionMaxLifespan() {
        return realm.getSsoSessionMaxLifespan();
    }

    @Override
    public void setSsoSessionMaxLifespan(int seconds) {
        realm.setSsoSessionMaxLifespan(seconds);
    }

    @Override
    public int getAccessTokenLifespan() {
        return realm.getAccessTokenLifespan();
    }

    @Override
    public void setAccessTokenLifespan(int seconds) {
        realm.setAccessTokenLifespan(seconds);
    }

    @Override
    public int getAccessCodeLifespan() {
        return realm.getAccessCodeLifespan();
    }

    @Override
    public void setAccessCodeLifespan(int seconds) {
        realm.setAccessCodeLifespan(seconds);
    }

    @Override
    public int getAccessCodeLifespanUserAction() {
        return realm.getAccessCodeLifespanUserAction();
    }

    @Override
    public void setAccessCodeLifespanUserAction(int seconds) {
        realm.setAccessCodeLifespanUserAction(seconds);
    }

    @Override
    public String getPublicKeyPem() {
        return realm.getPublicKeyPem();
    }

    @Override
    public void setPublicKeyPem(String publicKeyPem) {
        realm.setPublicKeyPem(publicKeyPem);
    }

    @Override
    public String getPrivateKeyPem() {
        return realm.getPrivateKeyPem();
    }

    @Override
    public void setPrivateKeyPem(String privateKeyPem) {
        realm.setPrivateKeyPem(privateKeyPem);
    }

    @Override
    public PublicKey getPublicKey() {
        return realm.getPublicKey();
    }

    @Override
    public void setPublicKey(PublicKey publicKey) {
        realm.setPublicKey(publicKey);
    }

    @Override
    public PrivateKey getPrivateKey() {
        return realm.getPrivateKey();
    }

    @Override
    public void setPrivateKey(PrivateKey privateKey) {
        realm.setPrivateKey(privateKey);
    }

    @Override
    public List<RequiredCredentialModel> getRequiredCredentials() {
        return realm.getRequiredCredentials();
    }

    @Override
    public void addRequiredCredential(String cred) {
         realm.addRequiredCredential(cred);
    }

    @Override
    public PasswordPolicy getPasswordPolicy() {
        return realm.getPasswordPolicy();
    }

    @Override
    public void setPasswordPolicy(PasswordPolicy policy) {
        realm.setPasswordPolicy(policy);
    }

    @Override
    public boolean validatePassword(UserModel userModel, String password) {
        if (provider.users().supports(Feature.VERIFY_CREDENTIALS)) {
            User user = provider.mappings().unwrap(userModel);
            return provider.users().verifyCredentials(user, new Credentials(UserCredentialModel.PASSWORD, password));
        } else {
            for (UserCredentialValueModel cred : userModel.getCredentialsDirectly()) {
                if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
                    return new Pbkdf2PasswordEncoder(cred.getSalt()).verify(password, cred.getValue());
                }
            }
            return false;
        }
    }

    @Override
    public boolean validateTOTP(UserModel userModel, String password, String token) {
        if (provider.users().supports(Feature.VERIFY_CREDENTIALS)) {
            User user = provider.mappings().unwrap(userModel);
            return provider.users().verifyCredentials(user, new Credentials(UserCredentialModel.PASSWORD, password),
                    new Credentials(UserCredentialModel.TOTP, token));
        } else {
            if (!validatePassword(userModel, password)) return false;
            for (UserCredentialValueModel cred : userModel.getCredentialsDirectly()) {
                if (cred.getType().equals(UserCredentialModel.TOTP)) {
                    return new TimeBasedOTP().validate(token, cred.getValue().getBytes());
                }
            }
            return false;
        }
    }

    @Override
    public UserModel getUser(String name) {
        return provider.getUserByUsername(name, this);
    }

    @Override
    public UserModel getUserByEmail(String email) {
        return provider.getUserByEmail(email, this);
    }

    @Override
    public UserModel getUserById(String name) {
        return provider.getUserById(name, this);
    }

    @Override
    public UserModel addUser(String id, String username, boolean addDefaultRoles) {
        if (id == null) {
            id = KeycloakModelUtils.generateId();
        }

        Set<String> initialRoles = new HashSet<String>();

        if (addDefaultRoles) {
            for (String r : realm.getDefaultRoles()) {
                initialRoles.add(realm.getRole(r).getId());
            }

            for (Application app : realm.getApplications()) {
                for (String r : app.getDefaultRoles()) {
                    initialRoles.add(app.getRole(r).getId());
                }
            }
        }

        return provider.mappings().wrap(this, provider.users().addUser(id, username, initialRoles, realm.getId()));
    }

    @Override
    public UserModel addUser(String username) {
        return addUser(null, username, true);
    }

    @Override
    public boolean removeUser(String name) {
        return provider.users().removeUser(name, realm.getId());
    }

    @Override
    public RoleModel getRoleById(String id) {
        return provider.mappings().wrap(provider.realms().getRoleById(id, realm.getId()));
    }

    @Override
    public List<String> getDefaultRoles() {
        return realm.getDefaultRoles();
    }

    @Override
    public void addDefaultRole(String name) {
        if (getRole(name) == null) {
            addRole(name);
        }

        realm.addDefaultRole(name);
    }

    @Override
    public void updateDefaultRoles(String[] defaultRoles) {
        for (String name : defaultRoles) {
            if (getRole(name) == null) {
                addRole(name);
            }
        }

        realm.updateDefaultRoles(defaultRoles);
    }

    @Override
    public ClientModel findClient(String clientId) {
        Client client = realm.findClient(clientId);
        if (client instanceof Application) {
            return provider.mappings().wrap((Application) client);
        } else if (client instanceof OAuthClient) {
            return provider.mappings().wrap((OAuthClient) client);
        } else {
            throw new IllegalArgumentException("Unsupported client type");
        }
    }

    @Override
    public Map<String, ApplicationModel> getApplicationNameMap() {
        return provider.mappings().wrap(realm.getApplicationNameMap());
    }

    @Override
    public List<ApplicationModel> getApplications() {
        return provider.mappings().wrapApps(realm.getApplications());
    }

    @Override
    public ApplicationModel addApplication(String name) {
        return addApplication(KeycloakModelUtils.generateId(), name);
    }

    @Override
    public ApplicationModel addApplication(String id, String name) {
        return provider.mappings().wrap(realm.addApplication(id, name));
    }

    @Override
    public boolean removeApplication(String id) {
        Application application = provider.realms().getApplicationById(id, realm.getId());
        if (application != null) {
            return realm.removeApplication(application);
        } else {
            return false;
        }
    }

    @Override
    public ApplicationModel getApplicationById(String id) {
        return provider.getApplicationById(id, this);
    }

    @Override
    public ApplicationModel getApplicationByName(String name) {
        return provider.mappings().wrap(realm.getApplicationByName(name));
    }

    @Override
    public void updateRequiredCredentials(Set<String> creds) {
        realm.updateRequiredCredentials(creds);
    }

    @Override
    public UserModel getUserBySocialLink(SocialLinkModel socialLink) {
        return provider.getUserBySocialLink(socialLink, this);
    }

    @Override
    public Set<SocialLinkModel> getSocialLinks(UserModel user) {
        return provider.getSocialLinks(user, this);
    }

    @Override
    public SocialLinkModel getSocialLink(UserModel user, String socialProvider) {
        return provider.getSocialLink(user, socialProvider, this);
    }

    @Override
    public void addSocialLink(UserModel user, SocialLinkModel socialLink) {
        user.setAttribute("keycloak.socialLink." + socialLink.getSocialProvider() + ".userId", socialLink.getSocialUserId());
        user.setAttribute("keycloak.socialLink." + socialLink.getSocialProvider() + ".username", socialLink.getSocialUsername());
    }

    @Override
    public boolean removeSocialLink(UserModel user, String socialProvider) {
        if (user.getAttribute("keycloak.socialLink." + socialProvider + ".userId") != null) {
            user.removeAttribute("keycloak.socialLink." + socialProvider + ".userId");
            user.removeAttribute("keycloak.socialLink." + socialProvider + ".username");
            return true;
        } else {
            return false;
        }
    }

    @Override
    public boolean isSocial() {
        return realm.isSocial();
    }

    @Override
    public void setSocial(boolean social) {
        realm.setSocial(social);
    }

    @Override
    public boolean isUpdateProfileOnInitialSocialLogin() {
        return realm.isUpdateProfileOnInitialSocialLogin();
    }

    @Override
    public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin) {
        realm.setUpdateProfileOnInitialSocialLogin(updateProfileOnInitialSocialLogin);
    }

    @Override
    public UsernameLoginFailureModel getUserLoginFailure(String username) {
        return provider.getUserLoginFailure(username, this);
    }

    @Override
    public UsernameLoginFailureModel addUserLoginFailure(String username) {
        return provider.addUserLoginFailure(username, this);
    }

    @Override
    public List<UsernameLoginFailureModel> getAllUserLoginFailures() {
        return provider.getAllUserLoginFailures(this);
    }

    @Override
    public List<UserModel> getUsers() {
        return provider.getUsers(this);
    }

    @Override
    public List<UserModel> searchForUser(String search) {
        return provider.searchForUser(search, this);
    }

    @Override
    public List<UserModel> searchForUserByAttributes(Map<String, String> attributes) {
        return provider.searchForUserByAttributes(attributes, this);
    }

    @Override
    public OAuthClientModel addOAuthClient(String name) {
        return addOAuthClient(KeycloakModelUtils.generateId(), name);
    }

    @Override
    public OAuthClientModel addOAuthClient(String id, String name) {
        return provider.mappings().wrap(realm.addOAuthClient(id, name));
    }

    @Override
    public OAuthClientModel getOAuthClient(String name) {
        return provider.mappings().wrap(realm.getOAuthClient(name));
    }

    @Override
    public OAuthClientModel getOAuthClientById(String id) {
        return provider.getOAuthClientById(id, this);
    }

    @Override
    public boolean removeOAuthClient(String id) {
        OAuthClient client = provider.realms().getOAuthClientById(id, realm.getId());
        if (client != null) {
            return realm.removeOAuthClient(client);
        } else {
            return false;
        }
    }

    @Override
    public List<OAuthClientModel> getOAuthClients() {
        return provider.mappings().wrapClients(realm.getOAuthClients());
    }

    @Override
    public Map<String, String> getSmtpConfig() {
        return realm.getSmtpConfig();
    }

    @Override
    public void setSmtpConfig(Map<String, String> smtpConfig) {
        realm.setSmtpConfig(smtpConfig);
    }

    @Override
    public Map<String, String> getSocialConfig() {
        return realm.getSocialConfig();
    }

    @Override
    public void setSocialConfig(Map<String, String> socialConfig) {
        realm.setSocialConfig(socialConfig);
    }

    @Override
    public Map<String, String> getLdapServerConfig() {
        return realm.getLdapServerConfig();
    }

    @Override
    public void setLdapServerConfig(Map<String, String> ldapServerConfig) {
        realm.setLdapServerConfig(ldapServerConfig);
    }

    @Override
    public List<AuthenticationProviderModel> getAuthenticationProviders() {
        return realm.getAuthenticationProviders();
    }

    @Override
    public void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders) {
        realm.setAuthenticationProviders(authenticationProviders);
    }

    @Override
    public String getLoginTheme() {
        return realm.getLoginTheme();
    }

    @Override
    public void setLoginTheme(String name) {
        realm.setLoginTheme(name);
    }

    @Override
    public String getAccountTheme() {
        return realm.getAccountTheme();
    }

    @Override
    public void setAccountTheme(String name) {
        realm.setAccountTheme(name);
    }

    @Override
    public String getAdminTheme() {
        return realm.getAdminTheme();
    }

    @Override
    public void setAdminTheme(String name) {
        realm.setAdminTheme(name);
    }

    @Override
    public String getEmailTheme() {
        return realm.getEmailTheme();
    }

    @Override
    public void setEmailTheme(String name) {
        realm.setEmailTheme(name);
    }

    @Override
    public int getNotBefore() {
        return realm.getNotBefore();
    }

    @Override
    public void setNotBefore(int notBefore) {
        realm.setNotBefore(notBefore);
    }

    @Override
    public boolean isAuditEnabled() {
        return realm.isAuditEnabled();
    }

    @Override
    public void setAuditEnabled(boolean enabled) {
        realm.setAuditEnabled(enabled);
    }

    @Override
    public long getAuditExpiration() {
        return realm.getAuditExpiration();
    }

    @Override
    public void setAuditExpiration(long expiration) {
        realm.setAuditExpiration(expiration);
    }

    @Override
    public Set<String> getAuditListeners() {
        return realm.getAuditListeners();
    }

    @Override
    public void setAuditListeners(Set<String> listeners) {
        realm.setAuditListeners(listeners);
    }

    @Override
    public ApplicationModel getMasterAdminApp() {
        return provider.mappings().wrap(realm.getMasterAdminApp());
    }

    @Override
    public void setMasterAdminApp(ApplicationModel app) {
        realm.setMasterAdminApp(provider.mappings().unwrap(app));
    }

    @Override
    public ClientModel findClientById(String id) {
        Application application = provider.realms().getApplicationById(id, realm.getId());
        if (application != null) {
            return provider.mappings().wrap(application);
        }

        OAuthClient client = provider.realms().getOAuthClientById(id, realm.getId());
        if (client != null) {
            return provider.mappings().wrap(client);
        }

        return null;
    }

    @Override
    public RoleModel getRole(String name) {
        return provider.mappings().wrap(realm.getRole(name));
    }

    @Override
    public RoleModel addRole(String name) {
        return addRole(KeycloakModelUtils.generateId(), name);
    }

    @Override
    public RoleModel addRole(String id, String name) {
        return provider.mappings().wrap(realm.addRole(id, name));

    }

    @Override
    public boolean removeRoleById(String id) {
        RoleModel role = getRoleById(id);
        if (role != null) {
            if (role.getContainer().removeRole(role)) {
                provider.users().onRoleRemoved(role.getId());
                return true;
            } else {
                return false;
            }
        }
        return false;
    }

    @Override
    public boolean removeRole(RoleModel role) {
        return removeRoleById(role.getId());
    }

    @Override
    public Set<RoleModel> getRoles() {
        return provider.mappings().wrap(realm.getRoles());
    }

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

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

}
