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

import org.apache.commons.io.IOUtils;
import org.jboss.arquillian.graphene.page.Page;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.RealmRepresentation;
import org.keycloak.testsuite.AbstractAuthTest;
import org.keycloak.testsuite.adapter.page.AppServerContextRoot;
import org.keycloak.testsuite.arquillian.annotation.AppServerContainer;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 *
 * @author tkyjovsk
 */
@AppServerContainer
public abstract class AbstractAdapterTest extends AbstractAuthTest {

    @Page
    protected AppServerContextRoot appServerContextRootPage;

    public static final String JBOSS_DEPLOYMENT_STRUCTURE_XML = "jboss-deployment-structure.xml";
    public static final URL jbossDeploymentStructure = AbstractServletsAdapterTest.class
            .getResource("/adapter-test/" + JBOSS_DEPLOYMENT_STRUCTURE_XML);
    public static final String TOMCAT_CONTEXT_XML = "context.xml";
    public static final URL tomcatContext = AbstractServletsAdapterTest.class
            .getResource("/adapter-test/" + TOMCAT_CONTEXT_XML);

    @Override
    public void addTestRealms(List<RealmRepresentation> testRealms) {
        addAdapterTestRealms(testRealms);
        for (RealmRepresentation tr : testRealms) {
            log.info("Setting redirect-uris in test realm '" + tr.getRealm() + "' as " + (isRelative() ? "" : "non-") + "relative");

            modifyClientRedirectUris(tr, "http://localhost:8080", "");
            modifyClientUrls(tr, "http://localhost:8080", "");

            if (isRelative()) {
                modifyClientRedirectUris(tr, appServerContextRootPage.toString(), "");
                modifyClientUrls(tr, appServerContextRootPage.toString(), "");
                modifyClientWebOrigins(tr, "8080", System.getProperty("auth.server.http.port", null));
                modifySamlMasterURLs(tr, "/", "http://localhost:" + System.getProperty("auth.server.http.port", null) + "/");
                modifySAMLClientsAttributes(tr, "8080", System.getProperty("auth.server.http.port", "8180"));
            } else {
                modifyClientRedirectUris(tr, "^(/.*/\\*)", appServerContextRootPage.toString() + "$1");
                modifyClientUrls(tr, "^(/.*)", appServerContextRootPage.toString() + "$1");
                modifyClientWebOrigins(tr, "8080", System.getProperty("app.server.http.port", null));
                modifySamlMasterURLs(tr, "8080", System.getProperty("auth.server.http.port", null));
                modifySAMLClientsAttributes(tr, "http://localhost:8080",  appServerContextRootPage.toString());
                modifyClientJWKSUrl(tr, "^(/.*)", appServerContextRootPage.toString() + "$1");
            }
            if ("true".equals(System.getProperty("auth.server.ssl.required"))) {
                tr.setSslRequired("all");
            }
        }
    }

    private void modifyClientJWKSUrl(RealmRepresentation realm, String regex, String replacement) {
        if (realm.getClients() != null) {
            realm.getClients().stream().
                    filter(client -> "client-jwt".equals(client.getClientAuthenticatorType()) && client.getAttributes().containsKey("jwks.url")).
                    forEach(client -> {
                Map<String, String> attr = client.getAttributes();
                attr.put("jwks.url", attr.get("jwks.url").replaceFirst(regex, replacement));
                client.setAttributes(attr);
            });
        }
    }

    public abstract void addAdapterTestRealms(List<RealmRepresentation> testRealms);

    public boolean isRelative() {
        return testContext.isRelativeAdapterTest();
    }

    protected void modifyClientRedirectUris(RealmRepresentation realm, String regex, String replacement) {
        if (realm.getClients() != null) {
            for (ClientRepresentation client : realm.getClients()) {
                List<String> redirectUris = client.getRedirectUris();
                if (redirectUris != null) {
                    List<String> newRedirectUris = new ArrayList<>();
                    for (String uri : redirectUris) {
                        newRedirectUris.add(uri.replaceAll(regex, replacement));
                    }
                    client.setRedirectUris(newRedirectUris);
                }
            }
        }
    }

    protected void modifyClientUrls(RealmRepresentation realm, String regex, String replacement) {
        if (realm.getClients() != null) {
            for (ClientRepresentation client : realm.getClients()) {
                String baseUrl = client.getBaseUrl();
                if (baseUrl != null) {
                    client.setBaseUrl(baseUrl.replaceAll(regex, replacement));
                }
                String adminUrl = client.getAdminUrl();
                if (adminUrl != null) {
                    client.setAdminUrl(adminUrl.replaceAll(regex, replacement));
                }
            }
        }
    }

    protected void modifyClientWebOrigins(RealmRepresentation realm, String regex, String replacement) {
        if (realm.getClients() != null) {
            for (ClientRepresentation client : realm.getClients()) {
                List<String> webOrigins = client.getWebOrigins();
                if (webOrigins != null) {
                    List<String> newWebOrigins = new ArrayList<>();
                    for (String uri : webOrigins) {
                        newWebOrigins.add(uri.replaceAll(regex, replacement));
                    }
                    client.setWebOrigins(newWebOrigins);
                }
            }
        }
    }

    protected void modifySAMLClientsAttributes(RealmRepresentation realm, String regex, String replacement) {
        if (realm.getClients() != null) {
            for (ClientRepresentation client : realm.getClients()) {
                if (client.getProtocol() != null && client.getProtocol().equals("saml")) {
                    log.info("Modifying attributes of SAML client: " + client.getClientId());
                    for (Map.Entry<String, String> entry : client.getAttributes().entrySet()) {
                        client.getAttributes().put(entry.getKey(), entry.getValue().replaceAll(regex, replacement));
                    }
                }
            }
        }
    }

    protected void modifySamlMasterURLs(RealmRepresentation realm, String regex, String replacement) {
        if (realm.getClients() != null) {
            for (ClientRepresentation client : realm.getClients()) {
                if (client.getProtocol() != null && client.getProtocol().equals("saml")) {
                    log.info("Modifying master URL of SAML client: " + client.getClientId());
                    String masterUrl = client.getAdminUrl();
                    if (masterUrl == null) {
                        masterUrl = client.getBaseUrl();
                    }
                    masterUrl = masterUrl.replaceFirst(regex, replacement);
                    client.setAdminUrl(masterUrl + ((!masterUrl.endsWith("/saml")) ? "/saml" : ""));
                }
            }
        }
    }

    /**
     * Modifies baseUrl, adminUrl and redirectUris for client based on real
     * deployment url of the app.
     *
     * @param realm
     * @param clientId
     * @param deploymentUrl
     */
    protected void fixClientUrisUsingDeploymentUrl(RealmRepresentation realm, String clientId, String deploymentUrl) {
        for (ClientRepresentation client : realm.getClients()) {
            if (clientId.equals(client.getClientId())) {
                if (client.getBaseUrl() != null) {
                    client.setBaseUrl(deploymentUrl);
                }
                if (client.getAdminUrl() != null) {
                    client.setAdminUrl(deploymentUrl);
                }
                List<String> redirectUris = client.getRedirectUris();
                if (redirectUris != null) {
                    List<String> newRedirectUris = new ArrayList<>();
                    for (String uri : redirectUris) {
                        newRedirectUris.add(deploymentUrl + "/*");
                    }
                    client.setRedirectUris(newRedirectUris);
                }
            }
        }
    }

    public static void addContextXml(Archive archive, String contextPath) {
        try {
            String contextXmlContent = IOUtils.toString(tomcatContext.openStream())
                    .replace("%CONTEXT_PATH%", contextPath);
            archive.add(new StringAsset(contextXmlContent), "/META-INF/context.xml");
        } catch (IOException ex) {
            throw new RuntimeException(ex);
        }
    }

}
