package com.aip.gateway.api.aip;

import com.aip.gateway.api.aip.model.AipConfig;
import com.aip.gateway.api.aip.utils.MemoryTokenCacheWithEviction;
import com.microsoft.aad.msal4j.*;
import com.microsoft.informationprotection.IAuthDelegate;
import com.microsoft.informationprotection.Identity;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.CompletableFuture;


@Slf4j
@Getter
@Setter
public class AuthDelegateImpl implements IAuthDelegate {

    private int lastErrNo;
    private String lastErrMsg;
    private AipConfig aipConfig;
    private ConfidentialClientApplication confidentialApp;
    private final String instance = "https://login.microsoftonline.com/";
    private final String authority = "https://login.windows.net/";
    private final String scope = "https://graph.microsoft.com/.default";

    public AuthDelegateImpl(AipConfig aipConfig) throws Exception {
        this.aipConfig = aipConfig;
        resetError();

        String cacheKey = aipConfig.getClientId() + "_" + aipConfig.getTenantId() + "_AppTokenCache";
        MemoryTokenCacheWithEviction memoryTokenCacheWithEviction = new MemoryTokenCacheWithEviction(cacheKey);

        if (aipConfig.getLoginType() == AipConfig.authLoginPassword) {
            confidentialApp = ConfidentialClientApplication.builder(
                            aipConfig.getClientId(),
                            ClientCredentialFactory.createFromSecret(aipConfig.getSecretValue()))
                    .authority(instance + aipConfig.getTenantId())
                    .setTokenCacheAccessAspect(memoryTokenCacheWithEviction)
                    .build();
        }
        else if (aipConfig.getLoginType() == AipConfig.authLoginCert) {
            InputStream certStream = new ByteArrayInputStream(Files.readAllBytes(Paths.get(aipConfig.getCertThumbPrint())));

            String password = "hanteinfo1234!";
            confidentialApp = ConfidentialClientApplication.builder(
                            aipConfig.getClientId(),
                            ClientCredentialFactory.createFromCertificate(certStream, password))
                    .authority(authority+aipConfig.getTenantId())
                    .setTokenCacheAccessAspect(memoryTokenCacheWithEviction)
                    .build();
        }
        else {
            throw new Exception("지원하지 않는 AIP 인증 방법 입니다.");
        }
    }
    public void resetError() {
        lastErrNo = 0;
        lastErrMsg = "";
    }
    public void setError(int errNo, String errMsg1, String errMsg2) {
        lastErrNo = errNo;
        if (errMsg2 == null || errMsg2.isEmpty()) {
            lastErrMsg = errMsg1;
        }
        else {
            lastErrMsg = errMsg1 + "\r\n" + errMsg2;
        }
        log.error("AuthDelegateImpl::setError ==> {}, {}", errNo, lastErrMsg);
    }
    @Override
    public String acquireToken(Identity identity, String authority, String resource, String claim) {
        return AcquireTokenByCertificate(identity, authority, resource, claim);
    }
    private String AcquireTokenByCertificate(Identity identity, String authority, String resource, String claims)
    {
        ////ddddddddddddddddddddddddddddddddd: https://login.windows.net/common, https://syncservice.o365syncservice.com/
        //log.error("ddddddddddddddddddddddddddddddddd: {}, {}", authority, resource);
        // AuthenticationResult result;
        // var authorityUri = new Uri(authority);
        // authority = $"https://{authorityUri.Host}/{_aipConfig.TenantId}";
        Set<String> scope = Collections.singleton("");
        if (resource.endsWith("/")){
            scope = Collections.singleton(resource + ".default");
        }
        else {
            scope = Collections.singleton(resource + "/.default");
        }
        try
        {
            ClientCredentialParameters clientCredentialParam = ClientCredentialParameters.builder(
//                            Collections.singleton(scope)
                            scope
                    )
                    .build();

            CompletableFuture<IAuthenticationResult> future = confidentialApp.acquireToken(clientCredentialParam);
            IAuthenticationResult result;
            result = future.get();
            return result.accessToken();
        }
        catch (Exception ex) {
            if (ex.getCause() instanceof MsalException) {
                setError(1, "AcquireTokenByCertificate::AcquireTokenByCertificate, Scope provided is not supported.", ex.getMessage());
            } else {
                setError(1, "AcquireTokenByCertificate::AcquireTokenByCertificate Failed.", ex.getMessage());
            }
        }

        return null;
    }
}
