/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.karaf.jaas.modules.syncope;

import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.util.EntityUtils;
import org.apache.karaf.jaas.boot.principal.GroupPrincipal;
import org.apache.karaf.jaas.boot.principal.RolePrincipal;
import org.apache.karaf.jaas.boot.principal.UserPrincipal;
import org.apache.karaf.jaas.modules.BackingEngine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.naming.OperationNotSupportedException;
import java.security.Principal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class SyncopeBackingEngine implements BackingEngine {

    private final Logger logger = LoggerFactory.getLogger(SyncopeBackingEngine.class);

    private String address;

    private DefaultHttpClient client;

    public SyncopeBackingEngine(String address, String adminUser, String adminPassword) {
        this.address = address;

        client = new DefaultHttpClient();
        Credentials creds = new UsernamePasswordCredentials(adminUser, adminPassword);
        client.getCredentialsProvider().setCredentials(AuthScope.ANY, creds);
    }

    public void addUser(String username, String password) {
        if (username.startsWith(GROUP_PREFIX)) {
            throw new IllegalArgumentException("Group prefix " + GROUP_PREFIX + " not permitted with Syncope backend");
        }
        HttpPost request = new HttpPost(address + "/users");
        request.setHeader("Content-Type", "application/xml");
        String userTO = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>" +
                "<user>" +
                "<attributes>" +
                "<attribute><readonly>false</readonly><schema>fullname</schema><value>" + username + "</value></attribute>" +
                "<attribute><readonly>false</readonly><schema>surname</schema><value>" + username + "</value></attribute>" +
                "<attribute><readonly>false</readonly><schema>userId</schema><value>" + username + "@karaf.apache.org</value></attribute>" +
                "</attributes>" +
                "<password>" + password + "</password>" +
                "<username>" + username + "</username>" +
                "</user>";
        try {
            StringEntity entity = new StringEntity(userTO);
            request.setEntity(entity);
            HttpResponse response = client.execute(request);
        } catch (Exception e) {
            logger.error("Can't add user {}", username, e);
            throw new RuntimeException("Can't add user " + username, e);
        }
    }

    public void deleteUser(String username) {
        if (username.startsWith(GROUP_PREFIX)) {
            throw new IllegalArgumentException("Group prefix " + GROUP_PREFIX + " not permitted with Syncope backend");
        }
        HttpDelete request = new HttpDelete(address + "/users/" + username);
        request.setHeader("Content-Type", "application/xml");
        try {
            client.execute(request);
        } catch (Exception e) {
            logger.error("Can't delete user {}", username, e);
            throw new RuntimeException("Can't delete user " + username, e);
        }
    }

    public List<UserPrincipal> listUsers() {
        List<UserPrincipal> users = new ArrayList<>();
        HttpGet request = new HttpGet(address + "/users");
        request.setHeader("Content-Type", "application/xml");
        try {
            HttpResponse response = client.execute(request);
            String responseTO = EntityUtils.toString(response.getEntity());
            if (responseTO != null && !responseTO.isEmpty()) {
                // extracting the user
                int index = responseTO.indexOf("<username>");
                while (index != -1) {
                    responseTO = responseTO.substring(index + "<username>".length());
                    int end = responseTO.indexOf("</username>");
                    if (end == -1) {
                        index = -1;
                    }
                    String username = responseTO.substring(0, end);
                    users.add(new UserPrincipal(username));
                    responseTO = responseTO.substring(end + "</username>".length());
                    index = responseTO.indexOf("<username>");
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Error listing users", e);
        }
        return users;
    }

    public List<RolePrincipal> listRoles(Principal principal) {
        List<RolePrincipal> roles = new ArrayList<>();
        HttpGet request = new HttpGet(address + "/users?username=" + principal.getName());
        request.setHeader("Content-Type", "application/xml");
        try {
            HttpResponse response  = client.execute(request);
            String responseTO = EntityUtils.toString(response.getEntity());
            if (responseTO != null && !responseTO.isEmpty()) {
                int index = responseTO.indexOf("<roleName>");
                while (index != 1) {
                    responseTO = responseTO.substring(index + "<roleName>".length());
                    int end = responseTO.indexOf("</roleName>");
                    if (end == -1) {
                        index = -1;
                        break;
                    }
                    String role = responseTO.substring(0, end);
                    roles.add(new RolePrincipal(role));
                    responseTO = responseTO.substring(end + "</roleName>".length());
                    index = responseTO.indexOf("<roleName>");
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("Error listing roles", e);
        }
        return roles;
    }

    public void addRole(String username, String role) {
        throw new RuntimeException("Roles management should be done on the Syncope side");
    }

    public void deleteRole(String username, String role) {
        throw new RuntimeException("Roles management should be done on the Syncope side");
    }

    public List<GroupPrincipal> listGroups(UserPrincipal principal) {
        return new ArrayList<GroupPrincipal>();
    }

    public void addGroup(String username, String group) {
        throw new RuntimeException("Group management is not supported by Syncope backend");
    }

    public void deleteGroup(String username, String group) {
        throw new RuntimeException("Group management is not supported by Syncope backend");
    }

    public void addGroupRole(String group, String role) {
        throw new RuntimeException("Group management is not supported by Syncope backend");
    }

    public void deleteGroupRole(String group, String role) {
        throw new RuntimeException("Group management is not supported by Syncope backend");
    }

    public Map<GroupPrincipal, String> listGroups() {
        throw new RuntimeException("Group management is not supported by Syncope backend");
    }

    public void createGroup(String group) {
            throw new RuntimeException("Group management is not supported by Syncope backend");
    }

}
