/*-
 * ~~~~~~licensing~~~~~~
 * connection-initializer
 * ==========
 * Copyright (C) 2020 - 2023 EmDev LLC
 * ==========
 * You may not use this file except in accordance with the License Terms of the Copyright
 * Holder located at: https://entaxy.ru/eula . All copyrights, all intellectual property
 * rights to the Software and any copies are the property of the Copyright Holder. Unless
 * it is explicitly allowed the Copyright Holder, the User is prohibited from using the
 * Software for commercial purposes to provide services to third parties.
 * 
 * The Copyright Holder hereby declares that the Software is provided on an "AS IS".
 * Under no circumstances does the Copyright Holder guarantee or promise that the
 * Software provided by him will be suitable or not suitable for the specific purposes
 * of the User, that the Software will meet all commercial and personal subjective
 * expectations of the User, that the Software will work properly, without technical
 * errors, quickly and uninterruptedly.
 * 
 * Under no circumstances shall the Copyright Holder or its Affiliates is not liable
 * to the User for any direct or indirect losses of the User, his expenses or actual
 * damage, including, downtime; loss of bussines; lost profit; lost earnings; loss
 * or damage to data, property, etc.
 * ~~~~~~/licensing~~~~~~
 */
package ru.entaxy.esb.platform.runtime.core.initializer.connection;

import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.List;
import java.util.stream.Collectors;

import org.osgi.framework.BundleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.gson.JsonObject;

import ru.entaxy.esb.platform.runtime.core.initializer.api.AbstractInitializer;
import ru.entaxy.esb.platform.runtime.core.initializer.api.InitializerException;
import ru.entaxy.esb.platform.runtime.core.management.connection.util.ConnectionManagerUtil;
import ru.entaxy.platform.base.support.FileUtils;
import ru.entaxy.platform.base.support.JSONUtils;
import ru.entaxy.platform.base.support.osgi.OSGIUtils;
import ru.entaxy.platform.core.artifact.ArtifactCoordinates;
import ru.entaxy.platform.core.producer.api.EntaxyProducerService;
import ru.entaxy.platform.core.producer.api.EntaxyProducerUtils;
import ru.entaxy.platform.core.producer.executor.objectmodel.ObjectModel;

public class ConnectionInitializer extends AbstractInitializer {

    private static final Logger log = LoggerFactory.getLogger(ConnectionInitializer.class);
    
    private static final String JSON_FILE_NAME = "entaxy-platform-connections.json";
    
    private static final String jsonBundlePath = "/connection/" + JSON_FILE_NAME;

    private static final String INIT_FILES_PATH = System.getProperty("karaf.etc") 
    		+ File.separator
    		+ "init";
    
    private static final String JSON_FILE_PATH = INIT_FILES_PATH 
    		+ File.separator
    		+ JSON_FILE_NAME;

    private EntaxyProducerService entaxyProducerService;
    
    @Override
    public void init() throws InitializerException {
        log.info("ConnectionInitializer started");
        log.info("-->> " + JSON_FILE_PATH);

        
        try {
	        entaxyProducerService = OSGIUtils.services().bundleContext(bundleContext)
	        		.ofClass(EntaxyProducerService.class)
	        		.waitService(50000)
	        		.get();
	        if (entaxyProducerService == null)
	        	throw new InitializerException(this, "Service EntaxyProducerService not found", "", null);
        } catch (Exception e) {
        	log.error("Error getting EntaxyProducerService", e);
        	throw new InitializerException(this, "Error getting EntaxyProducerService", "", e);
        }
        
        // first scan factory-base files
        try {
            scanAndInitFromFactoryFiles();
        } catch (Exception e) {
        	log.error("Error initializing connections from factory files", e);
            throw new InitializerException(this, "Can't create platform connections", "", e);
        }
        
        
        // then use the old one
        try {
            initPlatformConnections(bundleContext);
        } catch (Exception e) {
        	log.error("Error initializing connections from " + JSON_FILE_PATH, e);
            throw new InitializerException(this, "Can't create platform connections", "", e);
        }
    }

    @Override
    public void reinit() throws InitializerException {
    	// TODO Auto-generated method stub
    }

    private void scanAndInitFromFactoryFiles() throws Exception {
    	
    	File initDir = new File(INIT_FILES_PATH);
    	if (!initDir.exists() || !initDir.isDirectory())
    		return;
    	
    	File[] files = initDir.listFiles(new FilenameFilter() {
			
			@Override
			public boolean accept(File dir, String name) {
				return name.endsWith(".json") && !JSON_FILE_NAME.equals(name);
			}
		});
    	
    	if ((files == null) || (files.length==0))
    		return;
    	
    	for (int i=0; i<files.length; i++) {
    		log.debug("FOUND FILE :: " + files[i].getName());
    		
    		// 
    		// check if the file contains objects
    		// 
    		
    		FileUtils.FileHelper helper = new FileUtils.FileHelper(files[i].getAbsolutePath());
            if (!helper.isReadable()) {
            	// TODO throw exception
            	log.warn("Platform connections file {} is not readable", JSON_FILE_PATH);
            	continue;
            }
            JsonObject jsonData;
			try {
				URL url = files[i].toURI().toURL();
				jsonData = JSONUtils.getJsonRootObject(url);
			} catch (Exception e) {
				log.warn("Configuration loading failed:" + e.getMessage());
				continue;
			}
			
			ObjectModel objectModel = new ObjectModel();
			
			try {
				objectModel.load(jsonData);
				objectModel.checkRefs();
			} catch (Exception e) {
				log.warn("Error loading model from " + files[i].getAbsolutePath(), e);
				continue;
			}
			
			if (objectModel.objects.size() == 0)
				continue;
			
			log.debug("CONTAINS OBJECTS :: " + files[i].getName());


			//
			// check availability of factories
			// 
			List<String> factoryIds = objectModel.objects.stream()
					.map(obj -> obj.factoryId)
					.collect(Collectors.toList());
			for (String factoryId: factoryIds) {
				String message = "CHECK FACTORY :: " + factoryId + "..";
				if (entaxyProducerService.findFactoryById(factoryId) == null) {
					message += " NOT FOUND";
					log.debug(message);
					throw new InitializerException(this, "Factory not found: " + factoryId, "", null);
				}
				message += " FOUND";
				log.debug(message);
			}
			
			log.debug("FACTORIES AVAILABLE :: " + files[i].getName());
			
			//
			// produce objects
			// 
			
			EntaxyProducerUtils.InstructionsBuilder builder = EntaxyProducerUtils.instructions()
					.lifecycle("general")
        			.any()
        			.command("add-config")
        			.command("pre-generate")
        				.set(EntaxyProducerService.INSTRUCTIONS.PRINT_OUTPUT, false)
        			.command("build")
        				.set(EntaxyProducerService.INSTRUCTIONS.ARTIFACT.VERSION_POLICY
        						, ArtifactCoordinates.VERSION_POLICY_DATED_EMBEDDED)
        			.command("deploy")
        				.set("deployLocal", true); 
			
	        if (helper.isChanged()) {
	        	log.info("File is new or changed, install/update connections: " + files[i].getAbsolutePath());
	        	// we need to create/update connections with new timestamp
	        	String oldTimestamp = helper.getTimestamp();
	        	String newTimestamp = helper.updateTimestamp();
	        	
	        	builder
	        	.command("build")
					.set(EntaxyProducerService.INSTRUCTIONS.ARTIFACT.TIMESTAMP, newTimestamp)
	        	.command("install")
	        		.set("update", "")
	        		.set("installLocal", true)
	        		.set("startLevel", 75);

	        	String instructions = builder
	        			.getInstructionsString();
	        	
	        	entaxyProducerService.produce(jsonData, instructions);
	        	
	        	helper.updateMd5();
	        } else {
	        	log.info("File is not changed, install/check connection: " + files[i].getAbsolutePath());
	        	
	        	String oldTimestamp = helper.getTimestamp();

	        	builder
	        	.command("build")
					.set(EntaxyProducerService.INSTRUCTIONS.ARTIFACT.TIMESTAMP, oldTimestamp)
	        	.command("install")
	        		.set("installOnlyIfMissing", true)
					.set("installLocal", true)
	        		.set("startLevel", 75);

	        	String instructions = builder
	        			.getInstructionsString();
	        	
	        	entaxyProducerService.produce(jsonData, instructions);
	        }
			
			
		}

    	
    	
    }
    
    private void initPlatformConnections(BundleContext bundleContext) throws Exception {
    	checkAndPrepareFile(bundleContext);
        String json = getJsonAsString(bundleContext);
        
        FileUtils.FileHelper helper = new FileUtils.FileHelper(JSON_FILE_PATH);
        if (!helper.isReadable()) {
        	// TODO throw exception
        	log.error("Platform connections file {} is not readable", JSON_FILE_PATH);
        	return;
        }
        
        if (helper.isChanged()) {
        	log.info("File is new or changed, install/update connections");
        	// we need to create/update connections with new timestamp
        	String oldTimestamp = helper.getTimestamp();
        	String newTimestamp = helper.updateTimestamp();

        	// ConnectionManagerUtil.getService().createAndInstallConnections(json);
        	ConnectionManagerUtil.getService().installUpdateConnections(json, newTimestamp);
        	
        	helper.updateMd5();
        } else {
        	log.info("File is not changed, install/check connections");
        	// we need to create if absent connections with old timestamp
        	// ConnectionManagerUtil.getService().createAndInstallConnections(json);
        	ConnectionManagerUtil.getService().checkInstallConnections(json, helper.getTimestamp());
        }
        
        
    }

    private void checkAndPrepareFile(BundleContext context) throws IOException {
    	File f = new File(JSON_FILE_PATH);
    	if (!f.exists()) {
    		FileUtils.string2file(getJsonAsString(context), JSON_FILE_PATH);	
    	}
    }
    
    private String getJsonAsString(BundleContext context) throws IOException {
    	URL jsonUrl;
    	File f = new File(JSON_FILE_PATH);
    	if (f.exists() && f.canRead()) {
    		jsonUrl = f.toURI().toURL();
    		log.info("Path to json file: " + jsonUrl.toString());
    	} else {
	        jsonUrl = context.getBundle().getEntry(jsonBundlePath);
	        log.debug("Path to json file: " + jsonUrl.toString());
    	}
        InputStream inputStream = jsonUrl.openStream();
        byte[] bytes = inputStream.readAllBytes();
        String result = new String(bytes, Charset.defaultCharset());
        inputStream.close();
        return result;
    }

}
