package studycave.studycaverestservice.usos;


import io.restassured.response.Response;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.message.BasicNameValuePair;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import studycave.studycaverestservice.PropertiesLoader;
import studycave.studycaverestservice.model.user.User;
import studycave.studycaverestservice.model.user.UserRepository;
import studycave.studycaverestservice.model.user.UsosUser;
import studycave.studycaverestservice.utils.PasswordGenerator;

import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static io.restassured.RestAssured.get;
import static io.restassured.RestAssured.when;
import static studycave.studycaverestservice.utils.PasswordGenerator.*;

@Service
public class AuthorizationService {

    private BCryptPasswordEncoder bCryptPasswordEncoder;

    public AuthorizationService(BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }

    private static final String ENC = "UTF-8";
    private static final String HMAC_SHA1 = "HmacSHA1";
    private static Base64 base64 = new Base64();
    @Autowired
    private PropertiesLoader propertiesLoader;

    @Autowired
    private UserRepository userRepository;

    public TokenResponseDTO authorize(String oauth_token, String oauth_token_secret, String oauth_verifier) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
        List<NameValuePair> queryParams = new ArrayList<>();
        queryParams.add(new BasicNameValuePair("oauth_consumer_key", propertiesLoader.getKey()));
        queryParams.add(new BasicNameValuePair("oauth_nonce", "" + (int) (Math.random() * 100000000)));
        queryParams.add(new BasicNameValuePair("oauth_signature_method", "HMAC-SHA1"));
        queryParams.add(new BasicNameValuePair("oauth_timestamp", "" + (System.currentTimeMillis() / 1000)));
        queryParams.add(new BasicNameValuePair("oauth_token", oauth_token));
        queryParams.add(new BasicNameValuePair("oauth_verifier", oauth_verifier));
        queryParams.add(new BasicNameValuePair("oauth_version", "1.0"));
        String oauth_signature = getSignature("GET", URLEncoder.encode(propertiesLoader.getUsosApiURL() + "/oauth/access_token", ENC), URLEncoder.encode(URLEncodedUtils.format(queryParams, ENC), ENC), oauth_token_secret);
        queryParams.add(new BasicNameValuePair("oauth_signature", oauth_signature));
        String url = "/oauth/access_token?" + URLEncodedUtils.format(queryParams, ENC);
        String response = when().get(url).body().asString();
        if (response.contains("oauth_token") && response.contains("oauth_token_secret") && response.contains("&")) {
            String[] tokens = response.split("&");
            oauth_token = tokens[0].replace("oauth_token=", "");
            oauth_token_secret = tokens[1].replace("oauth_token_secret=", "");
            TokenResponseDTO tokenResponseDTO = createUserAccountAndReturnHeaderToken(oauth_token, oauth_token_secret);
            updateUserGroupsAndStudents(oauth_token, oauth_token_secret);
            //logOut(oauth_token, oauth_token_secret);
            return tokenResponseDTO;
        }
        return new TokenResponseDTO("authorization failed", null);
    }

    private void updateUserGroupsAndStudents(String oauth_token, String oauth_token_secret) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
        List<NameValuePair> queryParams = new ArrayList<>();
        queryParams.add(new BasicNameValuePair("fields", "course_unit_id|group_number|class_type|class_type_id|course_id|course_name|course_lang_id"));
        queryParams.add(new BasicNameValuePair("oauth_consumer_key", propertiesLoader.getKey()));
        queryParams.add(new BasicNameValuePair("oauth_nonce", "" + (int) (Math.random() * 100000000)));
        queryParams.add(new BasicNameValuePair("oauth_signature_method", "HMAC-SHA1"));
        queryParams.add(new BasicNameValuePair("oauth_timestamp", "" + (System.currentTimeMillis() / 1000)));
        queryParams.add(new BasicNameValuePair("oauth_token", oauth_token));
        queryParams.add(new BasicNameValuePair("oauth_version", "1.0"));
        String oauth_signature = getSignature("GET", URLEncoder.encode(propertiesLoader.getUsosApiURL() + "/groups/user", ENC), URLEncoder.encode(URLEncodedUtils.format(queryParams, ENC), ENC), oauth_token_secret);
        queryParams.add(new BasicNameValuePair("oauth_signature", oauth_signature));
        String url = "/groups/user?" + URLEncodedUtils.format(queryParams, ENC);

        String courses = when().get(url).body().asString();
        System.out.println(courses);
    }

    public String requestToken(String oauth_callback) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
        List<NameValuePair> queryParams = new ArrayList<>();
        queryParams.add(new BasicNameValuePair("oauth_callback", oauth_callback));
        queryParams.add(new BasicNameValuePair("oauth_consumer_key", propertiesLoader.getKey()));
        queryParams.add(new BasicNameValuePair("oauth_nonce", "" + (int) (Math.random() * 100000000)));
        queryParams.add(new BasicNameValuePair("oauth_signature_method", "HMAC-SHA1"));
        queryParams.add(new BasicNameValuePair("oauth_timestamp", "" + (System.currentTimeMillis() / 1000)));
        queryParams.add(new BasicNameValuePair("oauth_version", "1.0"));
        String oauth_signature = getSignature("GET", URLEncoder.encode(propertiesLoader.getUsosApiURL() + "/oauth/request_token", ENC), URLEncoder.encode(URLEncodedUtils.format(queryParams, ENC), ENC), "");
        queryParams.add(new BasicNameValuePair("oauth_signature", oauth_signature));
        String url = "/oauth/request_token?" + URLEncodedUtils.format(queryParams, ENC);

        String tokens = when().get(url).body().asString();

        return propertiesLoader.getUsosApiURL() + "/oauth/authorize?" + tokens;
    }

    public String logOut(String oauth_token, String oauth_token_secret) throws UnsupportedEncodingException, InvalidKeyException, NoSuchAlgorithmException {
        List<NameValuePair> queryParams = new ArrayList<>();
        queryParams.add(new BasicNameValuePair("oauth_consumer_key", propertiesLoader.getKey()));
        queryParams.add(new BasicNameValuePair("oauth_nonce", "" + (int) (Math.random() * 100000000)));
        queryParams.add(new BasicNameValuePair("oauth_signature_method", "HMAC-SHA1"));
        queryParams.add(new BasicNameValuePair("oauth_timestamp", "" + (System.currentTimeMillis() / 1000)));
        queryParams.add(new BasicNameValuePair("oauth_token", oauth_token));
        queryParams.add(new BasicNameValuePair("oauth_version", "1.0"));
        String oauth_signature = getSignature("GET", URLEncoder.encode(propertiesLoader.getUsosApiURL() + "/oauth/revoke_token", ENC), URLEncoder.encode(URLEncodedUtils.format(queryParams, ENC), ENC), oauth_token_secret);
        queryParams.add(new BasicNameValuePair("oauth_signature", oauth_signature));
        String url = "/oauth/revoke_token?" + URLEncodedUtils.format(queryParams, ENC);

        String loggedOut = when().get(url).body().asString();

        return loggedOut;
    }

    private TokenResponseDTO createUserAccountAndReturnHeaderToken(String oauth_token, String oauth_token_secret) throws IOException, InvalidKeyException, NoSuchAlgorithmException {
        List<NameValuePair> queryParams = new ArrayList<>();
        queryParams.add(new BasicNameValuePair("oauth_consumer_key", propertiesLoader.getKey()));
        queryParams.add(new BasicNameValuePair("oauth_nonce", "" + (int) (Math.random() * 100000000)));
        queryParams.add(new BasicNameValuePair("oauth_signature_method", "HMAC-SHA1"));
        queryParams.add(new BasicNameValuePair("oauth_timestamp", "" + (System.currentTimeMillis() / 1000)));
        queryParams.add(new BasicNameValuePair("oauth_token", oauth_token));
        queryParams.add(new BasicNameValuePair("oauth_version", "1.0"));
        String oauth_signature = getSignature("GET", URLEncoder.encode(propertiesLoader.getUsosApiURL() + "/users/user", ENC), URLEncoder.encode(URLEncodedUtils.format(queryParams, ENC), ENC), oauth_token_secret);

        queryParams.add(new BasicNameValuePair("oauth_signature", oauth_signature));
        String url = "/users/user?" + URLEncodedUtils.format(queryParams, ENC);

        Response response = get(url);
        JSONObject responseJSON = new JSONObject(response.getBody().print());
        String id = null;

        id = responseJSON.getString("id");


        Optional<User> userCheck = userRepository.findByUsername(id);
        String generatedPassword = PasswordGenerator.generatePassword(16, ALPHA_CAPS + ALPHA + NUMERIC);
        if (!userCheck.isPresent()) {
            User user = new User();
            UsosUser usosUser = new UsosUser(id, bCryptPasswordEncoder.encode(generatedPassword));
            user.setUsosUser(usosUser);
            user.setName(responseJSON.getString("first_name"));
            user.setSurname(responseJSON.getString("last_name"));
            user.setUsername(id);
            user = userRepository.save(user);
            String token = getAuthToken(user.getUsername(), generatedPassword);
            return new TokenResponseDTO(token, user.getUsername());
        }
        else {
            User user = userCheck.get();
            user.getUsosUser().setPassword(bCryptPasswordEncoder.encode(generatedPassword));
            user = userRepository.save(user);
            String token = getAuthToken(user.getUsername(), generatedPassword);
            return new TokenResponseDTO(token, user.getUsername());
        }
    }




    public String getSignature(String method, String url, String params, String secret_token)
            throws UnsupportedEncodingException, NoSuchAlgorithmException,
            InvalidKeyException {

        StringBuilder base = new StringBuilder();
        base.append(method);
        base.append("&");
        base.append(url);
        base.append("&");
        base.append(params);

        byte[] keyBytes = (propertiesLoader.getSecret() + "&" + secret_token).getBytes(ENC);

        SecretKey key = new SecretKeySpec(keyBytes, HMAC_SHA1);

        Mac mac = Mac.getInstance(HMAC_SHA1);
        mac.init(key);

        // encode it, base64 it, change it to string and return.
        return new String(base64.encode(mac.doFinal(base.toString().getBytes(
                ENC))), ENC).trim();
    }

    private String getAuthToken(String username, String password) throws IOException {
        HttpClient httpClient = HttpClientBuilder.create().build();
        HttpPost request = new HttpPost("http://localhost:8080/login");
        StringEntity params = new StringEntity("{\"username\":\"" + username + "\",\"password\":\"" + password + "\"} ");
        request.addHeader("content-type", "application/json");
        request.setEntity(params);
        HttpResponse httpResponse = httpClient.execute(request);
        String auth = httpResponse.getFirstHeader("authorization").getValue();
        return auth;
    }
}
