// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.athenz.identity;

import com.google.inject.Inject;
import com.yahoo.component.AbstractComponent;
import com.yahoo.container.jdisc.athenz.AthenzIdentityProvider;
import com.yahoo.log.LogLevel;
import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.athenz.tls.KeyStoreType;
import com.yahoo.vespa.athenz.tls.SslContextBuilder;

import javax.net.ssl.SSLContext;
import java.io.File;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;

/**
 * @author mortent
 * @author bjorncs
 */
public class SiaIdentityProvider extends AbstractComponent implements AthenzIdentityProvider {

    private static final Logger log = Logger.getLogger(SiaIdentityProvider.class.getName());

    private static final Duration REFRESH_INTERVAL = Duration.ofHours(1);

    private final AtomicReference<SSLContext> sslContext = new AtomicReference<SSLContext>();
    private final AthenzService service;
    private final File privateKeyFile;
    private final File certificateFile;
    private final File trustStoreFile;
    private final ScheduledExecutorService scheduler;

    @Inject
    public SiaIdentityProvider(SiaProviderConfig config) {
        this(new AthenzService(config.athenzDomain(), config.athenzService()),
             getPrivateKeyFile(config.keyPathPrefix(), config.athenzDomain(), config.athenzService()),
             getCertificateFile(config.keyPathPrefix(), config.athenzDomain(), config.athenzService()),
             new File(config.trustStorePath()),
             new ScheduledThreadPoolExecutor(1, runnable -> {
                 Thread thread = new Thread(runnable);
                 thread.setName("sia-identity-provider-sslcontext-updater");
                 return thread;
             }));
    }

    public SiaIdentityProvider(AthenzService service,
                               File privateKeyFile,
                               File certificateFile,
                               File trustStoreFile,
                               ScheduledExecutorService scheduler) {
        this.service = service;
        this.privateKeyFile = privateKeyFile;
        this.certificateFile = certificateFile;
        this.trustStoreFile = trustStoreFile;
        this.scheduler = scheduler;
        this.sslContext.set(createIdentitySslContext());
        scheduler.scheduleAtFixedRate(this::reloadSslContext, REFRESH_INTERVAL.toMinutes(), REFRESH_INTERVAL.toMinutes(), TimeUnit.MINUTES);
    }

    @Override
    public String getDomain() {
        return service.getDomain().getName();
    }

    @Override
    public String getService() {
        return service.getName();
    }

    @Override
    public SSLContext getIdentitySslContext() {
        return sslContext.get();
    }

    private SSLContext createIdentitySslContext() {
        return new SslContextBuilder()
                .withTrustStore(trustStoreFile, KeyStoreType.JKS)
                .withKeyStore(privateKeyFile, certificateFile)
                .build();
    }

    private void reloadSslContext() {
        log.log(LogLevel.DEBUG, "Updating SSLContext for identity " + service.getFullName());
        try {
            this.sslContext.set(createIdentitySslContext());
        } catch (Exception e) {
            log.log(LogLevel.SEVERE, "Failed to update SSLContext: " + e.getMessage(), e);
        }
    }

    private static File getCertificateFile(String rootPath, String domain, String service) {
        return Paths.get(rootPath, "certs", String.format("%s.%s.cert.pem", domain, service)).toFile();
    }

    private static File getPrivateKeyFile(String rootPath, String domain, String service) {
        return Paths.get(rootPath, "keys", String.format("%s.%s.key.pem", domain, service)).toFile();
    }

    @Override
    public void deconstruct() {
        try {
            scheduler.shutdownNow();
            scheduler.awaitTermination(90, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}

@@ -533,16 +533,43 @@
 o.File;%0A
+import java.nio.file.Path;%0A
 import j
@@ -588,16 +588,16 @@
 .Paths;%0A
-
 import j
@@ -1891,20 +1891,22 @@
     
-new 
+create
 Schedule
 dThr
@@ -1905,99 +1905,173 @@
 dule
-dThreadPoolExecutor(1, runnable -%3E %7B%0A                 Thread thread = new Thread(runnable);
+r());%0A    %7D%0A%0A    public SiaIdentityProvider(AthenzService service,%0A                               Path siaPath,%0A                               File trustStoreFile) %7B
 %0A   
@@ -2079,123 +2079,285 @@
     
-         thread.setName(%22sia-identity-provider-sslcontext-updater%22);%0A                 return thread;%0A             %7D
+this(service,%0A             getPrivateKeyFile(siaPath.toString(), service.getDomain().getName(), service.getName()),%0A             getCertificateFile(siaPath.toString(), service.getDomain().getName(), service.getName()),%0A             trustStoreFile,%0A             createScheduler(
 ));%0A
@@ -2900,32 +2900,32 @@
 ySslContext());%0A
-
         schedule
@@ -3048,32 +3048,331 @@
 INUTES);%0A    %7D%0A%0A
+    private static ScheduledThreadPoolExecutor createScheduler() %7B%0A        return new ScheduledThreadPoolExecutor(1, runnable -%3E %7B%0A            Thread thread = new Thread(runnable);%0A            thread.setName(%22sia-identity-provider-sslcontext-updater%22);%0A            return thread;%0A        %7D);%0A    %7D%0A%0A
     @Override%0A  

Add simplifed constructor to SiaIdentityProvider