/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.karavan;

import io.quarkus.runtime.Quarkus;
import io.quarkus.runtime.StartupEvent;
import io.vertx.mutiny.core.eventbus.EventBus;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.event.Observes;
import jakarta.enterprise.inject.Default;
import jakarta.inject.Inject;
import org.apache.camel.karavan.docker.DockerService;
import org.apache.camel.karavan.model.GitRepo;
import org.apache.camel.karavan.model.Project;
import org.apache.camel.karavan.model.ProjectFile;
import org.apache.camel.karavan.service.CodeService;
import org.apache.camel.karavan.service.ConfigService;
import org.apache.camel.karavan.service.GitService;
import org.apache.camel.karavan.service.ProjectService;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.eclipse.microprofile.health.HealthCheck;
import org.eclipse.microprofile.health.HealthCheckResponse;
import org.eclipse.microprofile.health.Readiness;
import org.jboss.logging.Logger;

import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

import static org.apache.camel.karavan.KaravanConstants.DEV;
import static org.apache.camel.karavan.KaravanEvents.NOTIFICATION_PROJECTS_STARTED;

@Default
@Readiness
@ApplicationScoped
public class KaravanStartupLoader implements HealthCheck {

    private static final Logger LOGGER = Logger.getLogger(KaravanStartupLoader.class.getName());

    @ConfigProperty(name = "karavan.environment", defaultValue = KaravanConstants.DEV)
    String environment;

    @Inject
    ProjectService projectService;

    @Inject
    KaravanCache karavanCache;

    @Inject
    DockerService dockerService;

    @Inject
    GitService gitService;

    @Inject
    CodeService codeService;

    @Inject
    EventBus eventBus;

    private AtomicBoolean ready = new AtomicBoolean(false);

    @Override
    public HealthCheckResponse call() {
        if (ready.get()) {
            return HealthCheckResponse.named("Projects").up().build();
        } else {
            return HealthCheckResponse.named("Projects").down().build();
        }
    }

    void onStart(@Observes StartupEvent ev) throws Exception {
        LOGGER.info("Starting " + ConfigService.getAppName() + " in " + environment + " env in " + (ConfigService.inKubernetes() ? "Kubernetes" : "Docker"));
        if (!ConfigService.inKubernetes() && !dockerService.checkDocker()){
            Quarkus.asyncExit();
        } else {
            LOGGER.info("Projects loading...");
            tryStart();
            eventBus.publish(NOTIFICATION_PROJECTS_STARTED, null);
            LOGGER.info("Projects loaded");
        }
    }

    public void tryStart() throws Exception {
        boolean git = gitService.checkGit();
        LOGGER.info("Starting Project service: git is " + (git ? "ready" : "not ready"));
        if (gitService.checkGit()) {
            if (karavanCache.getProjects().isEmpty()) {
                importAllProjects();
            }
            if (Objects.equals(environment, DEV)) {
                addKameletsProject();
                addTemplatesProject();
                addConfigurationProject();
                addServicesProject();
            }
            ready.set(true);
        } else {
            LOGGER.info("Projects are not ready");
            throw new Exception("Projects are not ready");
        }
    }

    private void importAllProjects() {
        LOGGER.info("Import projects from git: " + gitService.getGitConfig().getUri());
        try {
            List<GitRepo> repos = gitService.readProjectsToImport();
            repos.forEach(repo -> {
                Project project;
                String folderName = repo.getName();
                if (folderName.equals(Project.Type.templates.name())) {
                    project = new Project(Project.Type.templates.name(), "Templates", repo.getCommitId(), repo.getLastCommitTimestamp(), Project.Type.templates);
                } else if (folderName.equals(Project.Type.kamelets.name())) {
                    project = new Project(Project.Type.kamelets.name(), "Custom Kamelets", repo.getCommitId(), repo.getLastCommitTimestamp(), Project.Type.kamelets);
                } else if (folderName.equals(Project.Type.configuration.name())) {
                    project = new Project(Project.Type.configuration.name(), "Configuration", repo.getCommitId(), repo.getLastCommitTimestamp(), Project.Type.configuration);
                } else if (folderName.equals(Project.Type.services.name())) {
                    project = new Project(Project.Type.services.name(), "Dev Services", repo.getCommitId(), repo.getLastCommitTimestamp(), Project.Type.services);
                } else {
                    project = projectService.getProjectFromRepo(repo);
                }
                karavanCache.saveProject(project, true);

                repo.getFiles().forEach(repoFile -> {
                    ProjectFile file = new ProjectFile(repoFile.getName(), repoFile.getBody(), folderName, repoFile.getLastCommitTimestamp());
                    karavanCache.saveProjectFile(file, true, true);
                });
            });
        } catch (Exception e) {
            LOGGER.error("Error during project import", e);
        }
    }

    void addKameletsProject() {
        try {
            Project kamelets = karavanCache.getProject(Project.Type.kamelets.name());
            if (kamelets == null) {
                LOGGER.info("Add custom kamelets project");
                kamelets = new Project(Project.Type.kamelets.name(), "Custom Kamelets", "", Instant.now().toEpochMilli(), Project.Type.kamelets);
                karavanCache.saveProject(kamelets, true);
            }
        } catch (Exception e) {
            LOGGER.error("Error during custom kamelets project creation", e);
        }
    }

    void addTemplatesProject() {
        try {
            Project templates = karavanCache.getProject(Project.Type.templates.name());
            if (templates == null) {
                LOGGER.info("Add templates project");
                templates = new Project(Project.Type.templates.name(), "Templates", "", Instant.now().toEpochMilli(), Project.Type.templates);
                karavanCache.saveProject(templates, true);

                codeService.getTemplates().forEach((name, value) -> {
                    ProjectFile file = new ProjectFile(name, value, Project.Type.templates.name(), Instant.now().toEpochMilli());
                    karavanCache.saveProjectFile(file, false, true);
                });
            } else {
                codeService.getTemplates().forEach((name, value) -> {
                    ProjectFile f = karavanCache.getProjectFile(Project.Type.templates.name(), name);
                    if (f == null) {
                        LOGGER.info("Add new template " + name);
                        ProjectFile file = new ProjectFile(name, value, Project.Type.templates.name(), Instant.now().toEpochMilli());
                        karavanCache.saveProjectFile(file, false, true);
                    }
                });
            }
        } catch (Exception e) {
            LOGGER.error("Error during templates project creation", e);
        }
    }

    void addConfigurationProject() {
        try {
            Project configuration = karavanCache.getProject(Project.Type.configuration.name());
            if (configuration == null) {
                LOGGER.info("Add configuration project");
                configuration = new Project(Project.Type.configuration.name(), "Configuration", "", Instant.now().toEpochMilli(), Project.Type.configuration);
                karavanCache.saveProject(configuration, true);

                codeService.getConfigurationFiles().forEach((name, value) -> {
                    ProjectFile file = new ProjectFile(name, value, Project.Type.configuration.name(), Instant.now().toEpochMilli());
                    karavanCache.saveProjectFile(file, false, true);
                });
            } else {
                codeService.getConfigurationFiles().forEach((name, value) -> {
                    ProjectFile f = karavanCache.getProjectFile(Project.Type.configuration.name(), name);
                    if (f == null) {
                        LOGGER.info("Add new configuration " + name);
                        ProjectFile file = new ProjectFile(name, value, Project.Type.configuration.name(), Instant.now().toEpochMilli());
                        karavanCache.saveProjectFile(file, false, true);
                    }
                });
            }
        } catch (Exception e) {
            LOGGER.error("Error during configuration project creation", e);
        }
    }

    void addServicesProject() {
        try {
            Project services = karavanCache.getProject(Project.Type.services.name());
            if (services == null) {
                LOGGER.info("Add dev services project");
                services = new Project(Project.Type.services.name(), "Dev services", "", Instant.now().toEpochMilli(), Project.Type.services);
                karavanCache.saveProject(services, true);

                codeService.getDevServicesFiles().forEach((name, value) -> {
                    ProjectFile file = new ProjectFile(name, value, Project.Type.services.name(), Instant.now().toEpochMilli());
                    karavanCache.saveProjectFile(file, false, true);
                });
            } else {
                codeService.getDevServicesFiles().forEach((name, value) -> {
                    ProjectFile f = karavanCache.getProjectFile(Project.Type.services.name(), name);
                    if (f == null) {
                        LOGGER.info("Add new service " + name);
                        ProjectFile file = new ProjectFile(name, value, Project.Type.services.name(), Instant.now().toEpochMilli());
                        karavanCache.saveProjectFile(file, false, true);
                    }
                });
            }
        } catch (Exception e) {
            LOGGER.error("Error during services project creation", e);
        }
    }
}
