/*-
 * ~~~~~~licensing~~~~~~
 * artifact-management
 * ==========
 * Copyright (C) 2020 - 2025 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.platform.core.artifact.installer.builder.impl.local;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;

import org.apache.karaf.bundle.core.BundleService;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.startlevel.BundleStartLevel;
import org.osgi.framework.wiring.FrameworkWiring;

import ru.entaxy.platform.base.support.CommonUtils;
import ru.entaxy.platform.base.support.osgi.BundleUtils;
import ru.entaxy.platform.base.support.osgi.OSGIUtils;
import ru.entaxy.platform.core.artifact.installer.ArtifactInstaller;
import ru.entaxy.platform.core.artifact.installer.builder.InstallationResult;
import ru.entaxy.platform.core.artifact.installer.builder.impl.InstallationResultImpl;
import ru.entaxy.platform.core.artifact.installer.builder.typed.impl.CommonBundleInstallerHelper;
import ru.entaxy.platform.core.artifact.installer.builder.typed.impl.CommonBundleInstallerHelperConfig;

public class LocalCommonBundleInstallerHelper extends LocalTypedInstallerHelper 
		implements CommonBundleInstallerHelper {

	
	protected CommonBundleInstallerHelperConfig config;
	
	public static String bundleStatus(int bundleStatus) {
		if (bundleStatus == Bundle.ACTIVE)
			return "ACTIVE";
		if (bundleStatus == Bundle.INSTALLED)
			return "INSTALLED";
		if (bundleStatus == Bundle.RESOLVED)
			return "RESOLVED";
		if (bundleStatus == Bundle.STARTING)
			return "STARTING";
		if (bundleStatus == Bundle.STOPPING)
			return "STOPPING";
		if (bundleStatus == Bundle.UNINSTALLED)
			return "UNINSTALLED";
		return "INSTALLED";
	}
	
	@Override
	public void setConfig(CommonBundleInstallerHelperConfig config) {
		this.config = config;
	}
	
	@Override
	public InstallationResultImpl install(InstallationResultImpl installationResult) {
		
		Bundle existingBundle = null;
		
		if (this.config.isUpdate() || this.config.isUpdateOnly() || this.config.isInstallOnlyIfMissing()
				|| this.config.isStartBundleOnly() || this.config.isStopBundleOnly()) {
			
			BundleService bundleService = OSGIUtils.getService(BundleService.class);
			if (bundleService == null) {
				// TODO throw exception
				return installationResult
						.failed("Can't check existing: BundleService not available");
			} 
			
			String bundleId = this.config.getBundleId();
			
			if (!CommonUtils.isValid(bundleId)) {
				// TODO throw exception
				return installationResult
						.failed("Can't check existing: bundleId can not be defined");
			}
			
			// if not bundles found BundleService throws exception
			try {
				existingBundle = bundleService.getBundle(bundleId);
			} catch (Exception e) {
				// NOOP
			}
			
			if (existingBundle != null) {
				
				installationResult.object(existingBundle);
				
				if (this.config.isInstallOnlyIfMissing()) {
					
					try {
						if (this.config.isStartBundle())
							if (existingBundle.getState() != Bundle.ACTIVE)
								existingBundle.start();
					} catch (BundleException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}

					installationResult.property(InstallationResult.PROP_BUNDLE_STATUS
							, bundleStatus(existingBundle.getState()));
					
					if (this.config.isRefresh()) {
						FrameworkWiring wiring = existingBundle
								.getBundleContext().getBundle(0).adapt(FrameworkWiring.class);
				        wiring.refreshBundles(
				        		Arrays.asList(
				        				new Bundle[]{existingBundle}
				        		)
				       	);
					}
				    installationResult.notChanged();   
				    
				} else 
				if (this.config.isUpdate() || this.config.isUpdateOnly()) {
				
					try {
						URI uri = URI.create(this.config.getBundleLocation());
						URL url = uri.toURL();
						try (InputStream is = url.openStream()) {
				            if (this.config.isRawUpdate()) {
				            	existingBundle.update(is);
				            } else {
				                File file = BundleUtils.fixBundleWithUpdateLocation(is, url.toString());
				                try (FileInputStream fis = new FileInputStream(file)) {
				                	existingBundle.update(fis);
				                } catch (BundleException e) {
									// TODO Auto-generated catch block
									e.printStackTrace();
								}
				                file.delete();
				            }
				        } catch (Exception e) {
				        	throw e;
				        } 
						
						if (this.config.getStartLevel() > 0) {
							BundleStartLevel sl = existingBundle.adapt(BundleStartLevel.class);
							sl.setStartLevel(this.config.getStartLevel());
						}
						
						if (this.config.isStartBundle())
							if (existingBundle.getState() != Bundle.ACTIVE)
								existingBundle.start();

						installationResult.property(InstallationResult.PROP_BUNDLE_STATUS
								, bundleStatus(existingBundle.getState()));
	
						if (this.config.isRefresh()) {
							FrameworkWiring wiring = existingBundle
									.getBundleContext().getBundle(0).adapt(FrameworkWiring.class);
					        wiring.refreshBundles(
					        		Arrays.asList(
					        				new Bundle[]{existingBundle}
					        		)
					        	);
						}
						
						installationResult.updated();
						
					} catch (MalformedURLException e) {
						installationResult.error(e)
							.failed("Error updating bundle: URL malformed: " + this.config.getBundleLocation());
					} catch (IOException e) {
						installationResult.error(e)
							.failed("Error updating bundle: IOException");
					} catch (BundleException e) {
						installationResult.error(e)
							.failed("Error updating bundle: IOException");
					}
					
				} else 

				if (this.config.isStartBundleOnly()) {
					
					try {
						existingBundle.start();
						installationResult.started()
							.property(InstallationResult.PROP_BUNDLE_STATUS
									, bundleStatus(existingBundle.getState()));
					} catch (Exception e) {
						// TODO Auto-generated catch block
						installationResult.failed().error(e);
					}
					
				} else 
					
				if (this.config.isStopBundleOnly()) {

					try {
						existingBundle.stop();
						installationResult.stopped()
							.property(InstallationResult.PROP_BUNDLE_STATUS
									, bundleStatus(existingBundle.getState()));
					} catch (Exception e) {
						// TODO Auto-generated catch block
						installationResult.failed().error(e);
					}

				}	
					
				

				return installationResult;
			}	
			
		}
		
		if (this.config.isUpdateOnly()) {
			return installationResult.failed("UpdateOnly failed: bundle to update not found");
		}
		
		BundleContext bundleContext = FrameworkUtil.getBundle(ArtifactInstaller.class).getBundleContext();
		try {
			Bundle bundle = bundleContext.installBundle(this.config.getBundleLocation(), null);
			installationResult.object(bundle);
			if (this.config.getStartLevel() > 0) {
				BundleStartLevel sl = bundle.adapt(BundleStartLevel.class);
				sl.setStartLevel(this.config.getStartLevel());
			}
			if (this.config.isStartBundle()) {
				bundle.start();
			}

			installationResult.property(InstallationResult.PROP_BUNDLE_STATUS
					, bundleStatus(bundle.getState()));

		} catch (BundleException e) {
			installationResult.error(e)
				.failed("Installing bundle failed: BundleException");
		}

		return installationResult.installed();
	}
	@Override
	public InstallationResultImpl uninstall(InstallationResultImpl installationResult) {
		Bundle existingBundle = null;

		BundleService bundleService = OSGIUtils.getService(BundleService.class);
		if (bundleService == null) {
			// TODO throw exception
			return installationResult
					.failed("Can't check existing: BundleService not available");
		} 
		
		String bundleId = this.config.getBundleId();
		
		if (!CommonUtils.isValid(bundleId)) {
			// TODO throw exception
			return installationResult
					.failed("Can't check existing: bundleId can not be defined");
		}
		
		// if not bundles found BundleService throws exception
		try {
			existingBundle = bundleService.getBundle(bundleId);
		} catch (Exception e) {
			// NOOP
		}
		
		if (existingBundle != null) {
			try {
				existingBundle.uninstall();
				installationResult.uninstalled();
			} catch (Exception e) {
				installationResult.failed("Failed uninstalling bundle [" + bundleId + "]").error(e);
			}
		} else {
			installationResult.notChanged().message("BUNDLE NOT FOUND");
		}	
		
		return installationResult;
	}
}
