/*******************************************************************************
 * Copyright (c) 2012-2017 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.plugin.github.server;

import com.google.inject.Inject;
import com.google.inject.Singleton;

import org.eclipse.che.api.auth.oauth.OAuthTokenProvider;
import org.eclipse.che.api.auth.shared.dto.OAuthToken;
import org.eclipse.che.api.core.UnauthorizedException;
import org.eclipse.che.commons.env.EnvironmentContext;
import org.eclipse.che.commons.json.JsonHelper;
import org.eclipse.che.dto.server.DtoFactory;
import org.eclipse.che.api.git.GitUrlUtils;
import org.eclipse.che.plugin.github.shared.GitHubKey;
import org.eclipse.che.plugin.ssh.key.script.SshKeyUploader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.ws.rs.HttpMethod;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * @author Vladyslav Zhukovskii
 */
@Singleton
public class GitHubKeyUploader implements SshKeyUploader {

    private static final Logger  LOG                = LoggerFactory.getLogger(GitHubKeyUploader.class);
    private static final Pattern GITHUB_URL_PATTERN = Pattern.compile(".*github\\.com.*");

    private OAuthTokenProvider tokenProvider;

    @Inject
    public GitHubKeyUploader(OAuthTokenProvider tokenProvider) {
        this.tokenProvider = tokenProvider;
    }

    @Override
    public boolean match(String url) {
        return GitUrlUtils.isSSH(url) && GITHUB_URL_PATTERN.matcher(url).matches();
    }

    @Override
    public void uploadKey(String publicKey) throws IOException, UnauthorizedException {
        final OAuthToken token = tokenProvider.getToken("github", EnvironmentContext.getCurrent().getSubject().getUserId());

        if (token == null || token.getToken() == null) {
            LOG.debug("Token not found, user need to authorize to upload key.");
            throw new UnauthorizedException("To upload SSH key you need to authorize.");
        }

        StringBuilder answer = new StringBuilder();
        final String url = String.format("https://api.github.com/user/keys?access_token=%s", token.getToken());

        final List<GitHubKey> gitHubUserPublicKeys = getUserPublicKeys(url, answer);
        for (GitHubKey gitHubUserPublicKey : gitHubUserPublicKeys) {
            if (publicKey.startsWith(gitHubUserPublicKey.getKey())) {
                return;
            }
        }

        final Map<String, String> postParams = new HashMap<>(2);
        postParams.put("title", "IDE SSH Key (" + new SimpleDateFormat().format(new Date()) + ")");
        postParams.put("key", new String(publicKey.getBytes()));

        final String postBody = JsonHelper.toJson(postParams);

        LOG.debug("Upload public key: {}", postBody);

        int responseCode;
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection)new URL(url).openConnection();
            conn.setInstanceFollowRedirects(false);
            conn.setRequestMethod(HttpMethod.POST);
            conn.setRequestProperty(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON);
            conn.setRequestProperty(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON);
            conn.setRequestProperty(HttpHeaders.CONTENT_LENGTH, String.valueOf(postBody.length()));
            conn.setDoOutput(true);
            try (OutputStream out = conn.getOutputStream()) {
                out.write(postBody.getBytes());
            }
            responseCode = conn.getResponseCode();
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }

        LOG.debug("Upload key response code: {}", responseCode);

        if (responseCode != HttpURLConnection.HTTP_CREATED) {
            throw new IOException(String.format("%d: Failed to upload public key to http://github.com/", responseCode));
        }
    }

    private List<GitHubKey> getUserPublicKeys(String url, StringBuilder answer) {
        HttpURLConnection conn = null;
        try {
            conn = (HttpURLConnection)new URL(url).openConnection();
            conn.setInstanceFollowRedirects(false);
            conn.setRequestMethod(HttpMethod.GET);
            conn.setRequestProperty(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON);
            if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) {
                try (BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()))) {
                    String line;
                    while ((line = reader.readLine()) != null) {
                        answer.append(line).append('\n');
                    }
                }
                if (conn.getHeaderFields().containsKey("Link")) {
                    String strForParsing = conn.getHeaderFields().get("Link").get(0);
                    int indexNext = strForParsing.indexOf("rel=\"next\"");

                    if (indexNext != -1) {
                        String nextSubStr = strForParsing.substring(0, indexNext);
                        String nextPageLink = nextSubStr.substring(nextSubStr.indexOf("<") + 1, nextSubStr.indexOf(">"));

                        getUserPublicKeys(nextPageLink, answer);
                    }
                    int indexToReplace;
                    while ((indexToReplace = answer.indexOf("]\n[")) != -1) {
                        answer.replace(indexToReplace, indexToReplace + 3, ",");
                    }
                }
                return DtoFactory.getInstance().createListDtoFromJson(answer.toString(), GitHubKey.class);
            }
            return Collections.emptyList();
        } catch (IOException e) {
            LOG.error(e.getMessage(), e);
            return Collections.emptyList();
        } finally {
            if (conn != null) {
                conn.disconnect();
            }
        }
    }
}
