package com.hclsoftware.sso.kerberos.v2.services;

import com.hclsoftware.sso.kerberos.dto.ResponseDto;
import com.hclsoftware.sso.kerberos.utils.Serializer;
import com.ibm.as400.access.AS400;
import com.ibm.as400.access.AS400SecurityException;
import com.ibm.as400.security.auth.ProfileTokenCredential;
import org.ietf.jgss.GSSCredential;
import org.ietf.jgss.GSSManager;
import org.ietf.jgss.Oid;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.kerberos.authentication.JaasSubjectHolder;
import org.springframework.security.kerberos.authentication.KerberosUsernamePasswordAuthenticationToken;
import org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator;
import org.springframework.stereotype.Service;

import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosTicket;
import java.io.IOException;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;
import java.util.Base64;
import java.util.Optional;
import java.util.Set;

@Service
public class IBMiConnectorServiceV2 {


    @Value("${app.service-principal}")
    private String servicePrincipal;

    @Value("${app.keytab-location}")
    private String keytabLocation;

    @Value("${app.as400Host}")
    private String as400Host;

    @Autowired
    private SunJaasKerberosTicketValidator ticketValidator;


    public ProfileTokenCredential ibmIMc() {

        ProfileTokenCredential profileTokenCredential = null;

        GSSCredential gssCredential = getGssCredential();

        System.setProperty("com.ibm.as400.access.Trace.enabled", "true");
        AS400 as400 = null;

        try {
            as400 = new AS400(as400Host);
            System.out.println("as400.getGSSName() : "+as400.getGSSName());

            as400.setGSSCredential(gssCredential);
            profileTokenCredential = as400.getProfileToken();
            //profileTokenCredential.setTokenType(ProfileTokenCredential.TYPE_MULTIPLE_USE_RENEWABLE);


        } catch (AS400SecurityException | IOException | InterruptedException | NullPointerException e) {
            System.err.println(":: Exception ::" + e.toString());
            e.getCause();
            throw new UsernameNotFoundException(as400.getUserId()+" is not registered with IBM I system or timeZone/time is not is sync.", e.getCause());
        } finally {
            try {
                // Make sure to disconnect
                if (as400 != null) {
                    as400.disconnectAllServices();
                }
            } catch (Exception e) {
                System.err.println(":: Exception ::" + e.toString());
            }
        }
        return profileTokenCredential;

    }

    public GSSCredential getGssCredential() {
        Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication();

        //Oid spnegoMechOid = new Oid("1.3.6.1.5.5.2");

        KerberosUsernamePasswordAuthenticationToken existingAuth1 = (KerberosUsernamePasswordAuthenticationToken) existingAuth;

        JaasSubjectHolder jaasSubjectHolder = existingAuth1.getJaasSubjectHolder();
        Subject jaasSubject = jaasSubjectHolder.getJaasSubject();
        Set<Object> privateCredentials = jaasSubject.getPrivateCredentials();


        Optional<KerberosTicket> OptionalClientGssCreds = privateCredentials.stream()
                .filter(privateCredential ->
                        privateCredential instanceof KerberosTicket).map(privateCredential -> (KerberosTicket) privateCredential).findFirst();
        //KerberosTicket kerberosTicket = OptionalClientGssCreds.get();

        return Subject.doAs(jaasSubject, (PrivilegedAction<GSSCredential>) () -> {
            try {
                // Obtain GSSCredential from the Subject
                Oid krb5MechOid = new Oid("1.2.840.113554.1.2.2");

                return Subject.doAs(jaasSubject, (PrivilegedExceptionAction<GSSCredential>) () ->
                        GSSManager.getInstance().createCredential(null, GSSCredential.INDEFINITE_LIFETIME,
                                krb5MechOid, GSSCredential.INITIATE_ONLY));
            } catch (Exception e) {
                // Handle exception
                e.getCause();
                throw new UsernameNotFoundException(existingAuth1.getName()+" is not registered with IBM I system or timeZone/time is not is sync.", e.getCause());
            }
        });
    }



    /*for testing need to remove if doesn't work*/

    public AS400 getAs400() {

        System.out.println("from getAs400 ");
        GSSCredential gssCredential = getGssCredential();

        System.setProperty("com.ibm.as400.access.Trace.enabled", "true");
        AS400 as400 = new AS400(as400Host);
        System.out.println("as400.getGSSName() : "+as400.getGSSName());

        as400.setGSSCredential(gssCredential);


        return as400;

    }

    public ResponseDto getResponseDtoForIbmIMc() throws IOException {

        ProfileTokenCredential profileTokenCredential = null;

        GSSCredential gssCredential = getGssCredential();

        System.setProperty("com.ibm.as400.access.Trace.enabled", "true");
        AS400 as400 = new AS400(as400Host);


        as400.setGSSCredential(gssCredential);
        System.out.println("as400.getUserId() : "+as400.getUserId());

        try {
            profileTokenCredential = as400.getProfileToken();
            //profileTokenCredential.setTokenType(ProfileTokenCredential.TYPE_MULTIPLE_USE_RENEWABLE);


        } catch (AS400SecurityException | IOException | InterruptedException e) {
            System.err.println(":: Exception ::" + e.toString());
            e.printStackTrace();
        } finally {
            try {
                // Make sure to disconnect
                if (as400 != null) {
                    as400.disconnectAllServices();
                }
            } catch (Exception e) {
                System.err.println(":: Exception ::" + e.toString());
            }
        }
        byte[] serialize = Serializer.serialize(profileTokenCredential);//SerializationUtils.serialize(gssCredential);
        String token = Base64.getEncoder().encodeToString(serialize);
        return new ResponseDto(as400Host, as400.getUserId(), token);

    }

    // experimental
    public ResponseDto getResponseDtoForIbmIMc2() throws IOException {

        ProfileTokenCredential profileTokenCredential = null;

        GSSCredential gssCredential = getGssCredential();

        System.setProperty("com.ibm.as400.access.Trace.enabled", "true");
        AS400 as400 = new AS400(as400Host);

        as400.setGSSCredential(gssCredential);
        System.out.println("as400.getUserId() : "+as400.getUserId());

        try {
            profileTokenCredential = as400.getProfileToken();

        } catch ( AS400SecurityException | IOException | InterruptedException e) {
            System.err.println(":: Exception ::" + e.toString());
            e.printStackTrace();
        } finally {
            try {
                // Make sure to disconnect
                if (as400 != null) {
                    as400.disconnectAllServices();
                }
            } catch (Exception e) {
                System.err.println(":: Exception ::" + e.toString());
            }
        }

        byte[] serialize = Serializer.serialize(profileTokenCredential);//SerializationUtils.serialize(gssCredential);
        String token = Base64.getEncoder().encodeToString(serialize);
        return new ResponseDto(as400.getSystemName(), as400.getUserId(), token);

    }
}
