/*

*  Copyright (C) 2020, University of the Basque Country (UPV/EHU)
*
* Contact for licensing options: <licensing-mcpttclient(at)mcopenplatform(dot)com>
*
* This file is part of MCOP MCPTT Client
*
* This is free software: you can redistribute it and/or modify it under the terms of
* the GNU General Public License as published by the Free Software Foundation, either version 3
* of the License, or (at your option) any later version.
*
* This is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

package org.doubango.ngn.services.impl.ms;


import android.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.util.Log;

import net.openid.appauth.AppAuthConfiguration;
import net.openid.appauth.AuthState;
import net.openid.appauth.AuthorizationRequest;
import net.openid.appauth.AuthorizationServiceConfiguration;
import net.openid.appauth.ResponseTypeValues;
import net.openid.appauth.browser.BrowserWhitelist;
import net.openid.appauth.browser.VersionedBrowserMatcher;
import net.openid.appauth.connectivity.ConnectionBuilder;

import org.doubango.ngn.datatype.mcpttinfo.ContentType;
import org.doubango.ngn.datatype.mcpttinfo.McpttParamsType;
import org.doubango.ngn.datatype.mcpttinfo.McpttinfoType;
import org.doubango.ngn.datatype.mcpttinfo.ProtectionType;
import org.doubango.ngn.datatype.openId.CampsType;
import org.doubango.ngn.datatype.pocsettings.AmSettingType;
import org.doubango.ngn.datatype.pocsettings.EntityType;
import org.doubango.ngn.datatype.pocsettings.PocSettingsType;
import org.doubango.ngn.datatype.pocsettings.SelectedUserProfileIndex;
import org.doubango.utils.Utils;
import org.jose4j.base64url.internal.apache.commons.codec.binary.Base64;
import org.jose4j.json.internal.json_simple.JSONArray;
import org.jose4j.json.internal.json_simple.JSONObject;
import org.jose4j.json.internal.json_simple.JSONValue;
import org.jose4j.jwk.JsonWebKey;
import org.jose4j.lang.JoseException;
import org.json.JSONException;
import org.simpleframework.xml.Serializer;
import org.simpleframework.xml.convert.AnnotationStrategy;
import org.simpleframework.xml.core.Persister;
import org.simpleframework.xml.strategy.Strategy;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;


public class AuthenticacionUtils {
    private final static String TAG = Utils.getTAG(AuthenticacionUtils.class.getCanonicalName());
    private final static String AUTHENTICATION_PREFERENCE="AUTHENTICATION_PREFERENCE";
    private final static String AUTHENTICATION_PREFERENCE_AUTH_STATE="AUTHENTICATION_PREFERENCE_AUTH_STATE";
    private final static String AUTHENTICATION_PREFERENCE_AUTH_CAMPS="AUTHENTICATION_PREFERENCE_AUTH_CAMPS";
    private final static String SCOPES_DEFUALT_OLD="openid 3gpp:mcptt:ptt_server 3gpp:mcptt:key_management_server 3gpp:mcptt:config_management_server 3gpp:mcptt:group_management_server";
    private final static String SCOPES_DEFUALT="3gpp:mc:data_group_management_service 3gpp:mc:video_service 3gpp:mc:data_config_management_service openid 3gpp:mc:ptt_service 3gpp:mc:video_key_management_service 3gpp:mc:data_service 3gpp:mc:video_config_management_service 3gpp:mc:ptt_group_management_service 3gpp:mc:video_group_management_service 3gpp:mc:ptt_key_management_service 3gpp:mc:data_key_management_service 3gpp:mc:ptt_config_management_service";
    private static AuthState authStateNow;
    private static CampsType campsTypesNow;



    //INIT SAVE DATA AUTHENTICATION
    @NonNull
    public static AuthState readAuthState(Context context) throws JSONException {
        if(authStateNow==null){
            SharedPreferences authPrefs = context.getSharedPreferences(AUTHENTICATION_PREFERENCE,context.MODE_PRIVATE);
            String stateJson = authPrefs.getString(AUTHENTICATION_PREFERENCE_AUTH_STATE,null);
            if (stateJson != null){
                return AuthState.jsonDeserialize(stateJson);
            }
            else {
                return new AuthState();
            }
        }
        return authStateNow;

    }

    public static void writeAuthState( AuthState state,@NonNull Context context) {

        if(context!=null){
            String dataWrite=null;
            if(state!=null){
                authStateNow=state;
                dataWrite=authStateNow.jsonSerializeString();
            }else{
                authStateNow=state;
            }
            SharedPreferences authPrefs = context.getSharedPreferences(AUTHENTICATION_PREFERENCE, context.MODE_PRIVATE);
            authPrefs.edit()
                    .putString(AUTHENTICATION_PREFERENCE_AUTH_STATE,dataWrite )
                    .apply();
        }


    }


    protected static CampsType readAuthCamps(Context context) throws JSONException {
        if(campsTypesNow!=null){
            return campsTypesNow;
        }
        SharedPreferences authPrefs = context.getSharedPreferences(AUTHENTICATION_PREFERENCE,context.MODE_PRIVATE);
        String campsString = authPrefs.getString(AUTHENTICATION_PREFERENCE_AUTH_CAMPS,null);
        if (campsString != null){
            try {
                return CampsType.jsonDeserialize(campsString);
            } catch (IOException e) {
                return new CampsType();
            }
        }
        else {
            return new CampsType();
        }
    }

    protected static boolean writeAuthCamps(@NonNull CampsType campsType, Context context) {
        if(context!=null){
            campsTypesNow=campsType;
            SharedPreferences authPrefs = context.getSharedPreferences(AUTHENTICATION_PREFERENCE, context.MODE_PRIVATE);
            SharedPreferences.Editor editor = authPrefs.edit();
            try {


                if(campsType==null){
                    editor.remove(AUTHENTICATION_PREFERENCE_AUTH_CAMPS);
                    editor.apply();
                    return true;
                }else {
                    String data=null;
                    data=campsType.jsonSerializeString();
                    editor.putString(AUTHENTICATION_PREFERENCE_AUTH_CAMPS, data).apply();
                    return editor.commit();
                }
            } catch (IOException e) {
                Log.e(TAG,"Error in write Camps datas:"+e.getMessage());
                return false;
            } catch (JSONException e) {
                Log.e(TAG,"Error in write Camps datas 2");
                return false;
            }

        }
        return false;
    }


    //END SAVE DATA AUTHENTICATION
    public static JsonWebKey getProviderRSAJWK(String is, String keyID) throws java.text.ParseException {
        // Read all data from stream

        //StringBuilder sb = new StringBuilder();
        //try (Scanner scanner = new Scanner(is);) {
        //    while (scanner.hasNext()) {
        //        sb.append(scanner.next());
        //    }
        //}


        // Parse data as json
        //String jsonString = sb.toString();
        JSONObject json = null;
        json = (JSONObject) JSONValue.parse(is);

        // Find the RSA signing key
        JSONArray keyList = null;
        if(json==null){
            Log.e(TAG,"Error processing JSONObject");
            return null;
        }

        keyList = (JSONArray) json.get("keys");

        for (Object key : keyList) {
            JSONObject k = (JSONObject) key;

            if (k.get("kid").equals(keyID) && k.get("kty").equals("RSA")) {
                try {
                    JsonWebKey jwk = JsonWebKey.Factory.newJwk(k);
                    return jwk;
                } catch (JoseException e) {
                    e.printStackTrace();
                }

            }

        }


        return null;
    }


     //Generates network configuration in authentication
     //@return network configuration

    public static AppAuthConfiguration generateConfigureNet(){
        AppAuthConfiguration appAuthConfig = new AppAuthConfiguration.Builder()
                .setBrowserMatcher(new BrowserWhitelist(
                        VersionedBrowserMatcher.CHROME_CUSTOM_TAB,
                        VersionedBrowserMatcher.FIREFOX_BROWSER,
                        VersionedBrowserMatcher.SAMSUNG_BROWSER,
                        VersionedBrowserMatcher.SAMSUNG_CUSTOM_TAB))
                .setConnectionBuilder(new ConnectionBuilder() {
                    @NonNull
                    @Override
                    public HttpURLConnection openConnection(@NonNull Uri uri) throws IOException {
                        URL url = new URL(uri.toString());
                        HttpURLConnection connection =(HttpURLConnection) url.openConnection();


                        //if (connection instanceof HttpsURLConnection) {
                        //    HttpsURLConnection connection = (HttpsURLConnection) connection;
                        //    connection.setSSLSocketFactory(MySocketFactory.getInstance());
                        //}
                        return connection;
                    }
                })
                .build();

        return appAuthConfig;
    }
    //INIT utils xml
        //INIT MCPTT-INFO

    protected static McpttinfoType getMcpttinfoType(String string) throws Exception {
        return getMcpttinfoType(string.getBytes());
    }

    private static McpttinfoType getMcpttinfoType(byte[] bytes) throws Exception {
        return getMcpttinfoType(new ByteArrayInputStream(bytes));
    }
    private static McpttinfoType getMcpttinfoType(InputStream stream) throws Exception {
        if(stream==null)return null;
        Strategy strategy = new AnnotationStrategy();
        Serializer serializer = new Persister(strategy);
        return serializer.read(McpttinfoType.class,stream);
    }


    private static InputStream getOutputStreamOfMcpttinfoType(Context context, McpttinfoType mcpttinfoType) throws Exception {
        if(mcpttinfoType==null)return null;
        Strategy strategy = new AnnotationStrategy();
        Serializer serializer = new Persister(strategy);
        File outputDir = context.getCacheDir();
        File outputFile = File.createTempFile(String.valueOf(Calendar.getInstance().getTimeInMillis()), "txt", outputDir);
        serializer.write(mcpttinfoType,outputFile);
        return new FileInputStream(outputFile);
    }

    private static byte[] getBytesOfMcpttinfoType(Context context,McpttinfoType mcpttinfoType) throws Exception {
        InputStream inputStream=getOutputStreamOfMcpttinfoType(context,mcpttinfoType);
        if(inputStream==null)return null;
        return readBytes(inputStream);
    }

    private  static String  getStringOfMcpttinfoType(Context context,McpttinfoType mcpttinfoType) throws Exception {
        return new String(getBytesOfMcpttinfoType(context,mcpttinfoType)).trim();
    }

    private static McpttinfoType generateMcpttinfoType(CampsType campsTypeCurrent,String mcpttId,String mcpttClientIdString){
        McpttinfoType mcpttinfoType=new McpttinfoType();
        McpttParamsType mcpttParamsType=new McpttParamsType();
        mcpttinfoType.setMcpttParams(mcpttParamsType);
        if(mcpttClientIdString!=null &&
                !mcpttClientIdString.trim().isEmpty()
                ){
            ContentType mcpttClientId=new ContentType();
            mcpttClientId.setType(ProtectionType.Normal);
            mcpttParamsType.setMcpttClientId(mcpttClientId);
            mcpttClientId.setMcpttString(mcpttClientIdString.trim());
        }

        if(campsTypeCurrent!=null &&
                campsTypeCurrent.getAccessToken()!=null &&
                !campsTypeCurrent.getAccessToken().trim().isEmpty()){
            ContentType accessToken=new ContentType();
            accessToken.setType(ProtectionType.Normal);
            mcpttParamsType.setMcpttAccessToken(accessToken);
            accessToken.setMcpttString(campsTypeCurrent.getAccessToken().trim());
        }
        if(mcpttId!=null &&
                !mcpttId.trim().isEmpty()
                ){
            ContentType mcpttIdContentType=new ContentType();
            mcpttParamsType.setMcpttRequestUri(mcpttIdContentType);
            mcpttIdContentType.setType(ProtectionType.Normal);
            mcpttIdContentType.setMcpttURI(mcpttId);
        }

        return  mcpttinfoType;

    }

    protected static String generateMcpttinfoType(Context context,CampsType campsTypeCurrent,String mcpttIdString,String mcpttClientIdString){
        McpttinfoType mcpttinfoType=generateMcpttinfoType(campsTypeCurrent,mcpttIdString,mcpttClientIdString);
        if(mcpttinfoType!=null){
            try {
                return getStringOfMcpttinfoType(context,mcpttinfoType);
            } catch (Exception e) {
                Log.e(TAG,"Error generating Mcptt-info.");
            }
        }
        return null;
    }




    /**
     * In this function, if the campsTypeCurrentToGenerate is null, the device will generate mcpttinfo with token fail.
     * @param context
     * @param campsTypeCurrentToGenerate
     * @param mcpttClientIdString
     * @return
     */
    protected static String generateMcpttinfoType(@NonNull Context context,CampsType campsTypeCurrentToGenerate,@NonNull String mcpttId,@NonNull String mcpttClientIdString, boolean sendTokenFail){
        if((campsTypeCurrentToGenerate==null ||
                campsTypeCurrentToGenerate.getAccessToken()==null ||
                campsTypeCurrentToGenerate.getAccessToken().isEmpty())
                && sendTokenFail){
            if(mcpttId!=null){
                String data = String.format("{\"mcptt_id\":\"%s\",\"sub\":\"mcptt-client-A\",\"azp\":\"mcptt_client\",\"scope\":[\"3gpp:mcptt:ptt_server\",\"openid\",\"3gpp:mcptt:config_management_server\",\"3gpp:mcptt:key_management_server\",\"3gpp:mcptt:group_management_server\"],\"iss\":\"http:\\/\\/idms.organization.org:8080\\/openid-connect-server-webapp\\/\",\"exp\":1492767428,\"iat\":1492763828,\"jti\":\"b099481f-199b-41f9-8396-48eed364b288\",\"client_id\":\"mcptt_client\"}",
                        mcpttId);
                byte[]   bytesEncoded = Base64.encodeBase64(data .getBytes());
                String stringEncoded = new String(bytesEncoded);
                if(!stringEncoded.isEmpty()){
                    CampsType campsType=new CampsType();
                    String accessTokenFail=String.format("eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.%s.Q3RO7otkthtACrL9tya9-CRn9rtQBoH2XC4lZJCoTau4SPQ2gTllT2qJRSg0ciNgNgj1zq_cmnZZ1mM7E3HME_4gM0ATyHPZg5hv0gIquKvZUDs6sDZDDcmHwZTg6koZYv-XaQxtQCZwmyZ8OJXuQELaYAJ2rBaB0EnubrTmZdHKnvnWzpIjz1skI8AOnfBM8ixisBKpUaTi3TLETLmGJDY_k6YdPo5z18kZ_2SppJXPLOcxr7Z4r2VmsD3ZYSq_cnZwPcC_IhoLObfrW_N-Mki-lqRd5nw4TyURMLbuZRaRVnC7aUQBempb31OztjEm6y_UPtyxb_qQ9p8cTO_E6A",
                            stringEncoded);
                    if(accessTokenFail!=null && !accessTokenFail.isEmpty()){
                        Log.d(TAG,"Send access token Fail");
                        campsType.setAccessToken(accessTokenFail);
                        return  generateMcpttinfoType(context,campsType,mcpttId,mcpttClientIdString);
                    }
                }
            }

        }

        return generateMcpttinfoType(context,campsTypeCurrentToGenerate,mcpttId,mcpttClientIdString);
    }
        //END MCPTT-INFO

        //INIT POC-SETTINGS
    private static PocSettingsType generatePocSettingsType(boolean isManual,int indexUserProfile){
        PocSettingsType pocSettingsType=new PocSettingsType();
        ArrayList<EntityType> entityTypes=new ArrayList<>();
        pocSettingsType.setEntity(entityTypes);
        EntityType entity=new EntityType();
        entityTypes.add(entity);
        AmSettingType answerMode=new AmSettingType();
        entity.setAmSettings(answerMode);
        entity.setId(randomStringUUID());
        if(isManual){
            answerMode.setAnswerMode("manual");
        }else{
            answerMode.setAnswerMode("automatic");
        }
        if(indexUserProfile>=0){
            SelectedUserProfileIndex selectedUserProfileIndex=new SelectedUserProfileIndex();
            selectedUserProfileIndex.setUserProfileIndex(indexUserProfile);
            entity.setSelectedUserProfileIndex(new SelectedUserProfileIndex());
        }
        return pocSettingsType;
    }

    protected static String generatePocSettingsType(@NonNull Context context,boolean isManual,int indexUserProfile){
        PocSettingsType pocSettingsType=generatePocSettingsType(isManual,indexUserProfile);
        if(pocSettingsType!=null){
            try {
                return getStringOfPocSettingsType(context,pocSettingsType);
            } catch (Exception e) {
                Log.e(TAG,"Invalid parameters to generate poc-settings: "+e.getMessage());
            }
        }
        return null;
    }

    protected static String generatePocSettingsType(@NonNull Context context,boolean isManual){
        PocSettingsType pocSettingsType=generatePocSettingsType(isManual,-1);
        if(pocSettingsType!=null){
            try {
                return getStringOfPocSettingsType(context,pocSettingsType);
            } catch (Exception e) {
                Log.e(TAG,"Invalid parameters to generate poc-settings: "+e.getMessage());
            }
        }
        return null;
    }

    protected static PocSettingsType getPocSettingsType(String string) throws Exception {
        return getPocSettingsType(string.getBytes());
    }

    protected static PocSettingsType getPocSettingsType(byte[] bytes) throws Exception {
        return getPocSettingsType(new ByteArrayInputStream(bytes));
    }
    protected static PocSettingsType getPocSettingsType(InputStream stream) throws Exception {
        if(stream==null)return null;
        Strategy strategy = new AnnotationStrategy();
        Serializer serializer = new Persister(strategy);
        return serializer.read(PocSettingsType.class,stream);
    }


    protected static InputStream getOutputStreamOfPocSettingsType(Context context, PocSettingsType pocSettingsType) throws Exception {
        if(pocSettingsType==null)return null;
        Strategy strategy = new AnnotationStrategy();
        Serializer serializer = new Persister(strategy);
        File outputDir = context.getCacheDir(); // context being the Activity pointer
        File outputFile = File.createTempFile(String.valueOf(Calendar.getInstance().getTimeInMillis()), "txt", outputDir);
        serializer.write(pocSettingsType,outputFile);
        InputStream inputStream = new FileInputStream(outputFile);
        return inputStream;
    }

    protected static byte[] getBytesOfPocSettingsType(Context context,PocSettingsType pocSettingsType) throws Exception {
        InputStream inputStream=getOutputStreamOfPocSettingsType(context,pocSettingsType);
        if(inputStream==null)return null;
        return readBytes(inputStream);
    }

    protected  static String  getStringOfPocSettingsType(Context context,PocSettingsType pocSettingsType) throws Exception {
        return new String(getBytesOfPocSettingsType(context,pocSettingsType)).trim();
    }
        //END POC-SETTINGS

    private static byte[] readBytes(InputStream inputStream) throws IOException {

        ByteArrayOutputStream byteBuffer = new ByteArrayOutputStream();


        int bufferSize = 1024;
        byte[] buffer = new byte[bufferSize];


        int len = 0;
        while ((len = inputStream.read(buffer)) != -1) {
            byteBuffer.write(buffer, 0, len);
        }


        return byteBuffer.toByteArray();
    }
    //END utils xml

    private static String randomStringUUID() {
        //
        // Creating a random UUID (Universally unique identifier).
        //
        UUID uuid = UUID.randomUUID();
        String randomUUIDString = uuid.toString();
        return randomUUIDString;
    }


    protected static String getDatas(Context context,Calendar calendar){
        if(context==null)return "";
        SimpleDateFormat format1 = new SimpleDateFormat("yyyy-MM-dd HH:mm");
        String date=format1.format(calendar.getTime());
        return date;
    }
    protected static AuthorizationRequest getAuthorizationRequest(@NonNull AuthorizationServiceConfiguration serviceConfiguration, @NonNull AuthorizationServiceConfiguration authorizationServiceConfiguration, @NonNull String client_id, @NonNull Uri redirectUri){
        return  getAuthorizationRequest(  serviceConfiguration,   authorizationServiceConfiguration,   client_id,   redirectUri, false);

        }


        protected static AuthorizationRequest getAuthorizationRequest(@NonNull AuthorizationServiceConfiguration serviceConfiguration, @NonNull AuthorizationServiceConfiguration authorizationServiceConfiguration, @NonNull String client_id, @NonNull Uri redirectUri,boolean useClientSecret){

        Map<String, String> additionalParameters=new HashMap<>();
        if(useClientSecret){
            additionalParameters.put("acr_values","3gpp:acr:password");
        }

        AuthorizationRequest req = new AuthorizationRequest.Builder(
                authorizationServiceConfiguration,
                client_id,
                ResponseTypeValues.CODE,
                redirectUri).setScope(SCOPES_DEFUALT)
                //.setAdditionalParameters(additionalParameters)
                .build();
        return req;
    }


}