package io.cattle.platform.systemstack.catalog.impl;

import static io.cattle.platform.core.model.tables.StackTable.*;

import io.cattle.platform.archaius.util.ArchaiusUtil;
import io.cattle.platform.core.addon.CatalogTemplate;
import io.cattle.platform.core.constants.ServiceConstants;
import io.cattle.platform.core.dao.GenericResourceDao;
import io.cattle.platform.core.model.ProjectTemplate;
import io.cattle.platform.core.model.Stack;
import io.cattle.platform.json.JsonMapper;
import io.cattle.platform.object.ObjectManager;
import io.cattle.platform.object.process.ObjectProcessManager;
import io.cattle.platform.object.util.DataAccessor;
import io.cattle.platform.systemstack.catalog.CatalogService;
import io.cattle.platform.systemstack.model.Template;
import io.cattle.platform.systemstack.model.TemplateCollection;
import io.cattle.platform.util.type.CollectionUtils;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.inject.Inject;

import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.fluent.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.yaml.snakeyaml.Yaml;

import com.netflix.config.DynamicBooleanProperty;
import com.netflix.config.DynamicStringProperty;

public class CatalogServiceImpl implements CatalogService {

    private static DynamicStringProperty CATALOG_VERSION_URL = ArchaiusUtil.getString("system.stack.catalog.versions.url");
    private static DynamicStringProperty CATALOG_RESOURCE_URL = ArchaiusUtil.getString("system.stack.catalog.url");
    private static DynamicStringProperty CATALOG_RESOURCE_VERSION = ArchaiusUtil.getString("rancher.server.version");
    private static final DynamicBooleanProperty LAUNCH_CATALOG = ArchaiusUtil.getBoolean("catalog.execute");
    private static final Logger log = LoggerFactory.getLogger(CatalogServiceImpl.class);

    @Inject
    JsonMapper jsonMapper;

    @Inject
    GenericResourceDao resourceDao;

    @Inject
    ObjectManager objectManager;

    @Inject
    ObjectProcessManager processManager;

    boolean firstCall = true;

    @Override
    public Map<String, CatalogTemplate> resolvedExternalIds(List<CatalogTemplate> templates) throws IOException {
        Map<String, CatalogTemplate> result = new HashMap<>();

        for (CatalogTemplate template : templates) {
            String externalId = resolveExternalId(template);
            if (StringUtils.isNotBlank(externalId)) {
                result.put(externalId, template);
            }
        }

        return result;
    }

    private String resolveExternalId(CatalogTemplate template) throws IOException {
        if (StringUtils.isNotBlank(template.getTemplateId()) || StringUtils.isNotBlank(template.getTemplateVersionId())) {
            return resolveUsingCatalog(template);
        }

        return DigestUtils.md5Hex(template.getDockerCompose() + template.getRancherCompose());
    }

    protected void appendVersionCheck(StringBuilder catalogTemplateUrl) {
        String minVersion = CATALOG_RESOURCE_VERSION.get();
        if (StringUtils.isNotBlank(minVersion)) {
            catalogTemplateUrl.append("?rancherVersion=").append(minVersion);
        }
    }

    private String resolveUsingCatalog(CatalogTemplate catalogTemplate) throws IOException {
        if (!LAUNCH_CATALOG.get()) {
            return null;
        }
        if (StringUtils.isNotBlank(catalogTemplate.getTemplateVersionId())) {
            return String.format("catalog://%s", catalogTemplate.getTemplateVersionId());
        }

        //get the latest version from the catalog template
        Template template = getTemplateById(catalogTemplate.getTemplateId());
        return getDefaultOrLatestTemplateExternalId(template);
    }

    protected String getDefaultOrLatestTemplateExternalId(Template template) throws IOException {
        template = getDefaultOrLatestTemplate(template);
        return template == null ? null : String.format("catalog://%s", template.getId());
    }

    protected Template getDefaultOrLatestTemplate(Template template) throws IOException {
        if (template == null || template.getVersionLinks() == null) {
            return null;
        }

        String versionUrl = null;
        String defaultVersionURL = template.getVersionLinks().get(template.getDefaultVersion());
        if (StringUtils.isNotBlank(defaultVersionURL)) {
            versionUrl = defaultVersionURL;
        } else {
            long maxVersion = 0;
            for (String url : template.getVersionLinks().values()) {
                long currentMaxVersion = Long.valueOf(url.substring(url.lastIndexOf(":") + 1, url.length()));
                if (currentMaxVersion >= maxVersion) {
                    maxVersion = currentMaxVersion;
                    versionUrl = url;
                }
            }
        }

        return getTemplateAtURL(versionUrl);
    }

    protected Template getTemplateById(String id) throws IOException {
        StringBuilder catalogTemplateUrl = new StringBuilder(CATALOG_RESOURCE_URL.get());
        catalogTemplateUrl.append(id);
        appendVersionCheck(catalogTemplateUrl);
        return getTemplateAtURL(catalogTemplateUrl.toString());
    }

    protected Template getTemplateVersionById(String id) throws IOException {
        if (StringUtils.isBlank(id)) {
            return null;
        }
        id = StringUtils.removeStart(id, "catalog://");

        StringBuilder catalogTemplateVersionUrl = new StringBuilder(CATALOG_VERSION_URL.get());
        catalogTemplateVersionUrl.append(id);
        appendVersionCheck(catalogTemplateVersionUrl);
        return getTemplateAtURL(catalogTemplateVersionUrl.toString());
    }

    protected Template getTemplateAtURL(String url) throws IOException {
        if (url == null) {
            return null;
        }

        return Request.Get(url).execute().handleResponse(new ResponseHandler<Template>() {
            @Override
            public Template handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
                if (response.getStatusLine().getStatusCode() != 200) {
                    return null;
                }
                return jsonMapper.readValue(response.getEntity().getContent(), Template.class);
            }
        });
    }

    @Override
    public String getDefaultExternalId(Stack stack) throws IOException {
        Template template = getDefaultTemplateVersion(stack);
        return template == null ? null : "catalog://" + template.getId();
    }

    protected Template getDefaultTemplateVersion(Stack stack) throws IOException {
        Template template = getTemplateVersionById(stack.getExternalId());
        if (template == null || template.getLinks() == null && !template.getLinks().containsKey("template")) {
            return null;
        }

        String url = template.getLinks().get("template");
        if (StringUtils.isBlank(url)) {
            return null;
        }
        StringBuilder builder = new StringBuilder(url);
        appendVersionCheck(builder);
        return getDefaultOrLatestTemplate(getTemplateAtURL(builder.toString()));
    }

    @Override
    public Stack upgrade(Stack stack) throws IOException {
        Template template = getDefaultTemplateVersion(stack);
        if (template == null) {
            return null;
        }

        processManager.scheduleProcessInstance(ServiceConstants.PROCESS_STACK_UPGRADE, stack,
                CollectionUtils.asMap(
                   ServiceConstants.STACK_FIELD_DOCKER_COMPOSE, template.getDockerCompose(),
                   ServiceConstants.STACK_FIELD_RANCHER_COMPOSE, template.getRancherCompose(),
                   ServiceConstants.STACK_FIELD_ENVIRONMENT, DataAccessor.fieldMap(stack, ServiceConstants.STACK_FIELD_ENVIRONMENT),
                   ServiceConstants.STACK_FIELD_EXTERNAL_ID, "catalog://" + template.getId()));

        return stack;
    }

    @Override
    public Stack deploy(Long accountId, CatalogTemplate catalogTemplate) throws IOException {
        String externalId = resolveExternalId(catalogTemplate);
        if (externalId == null) {
            return null;
        }

        String dockerCompose = catalogTemplate.getDockerCompose();
        String rancherCompose = catalogTemplate.getRancherCompose();
        Template template = null;
        if (externalId.startsWith("catalog://")) {
            template = getTemplateAtURL(CATALOG_RESOURCE_URL.get() + StringUtils.removeStart(externalId, "catalog://"));
            if (template != null) {
                dockerCompose = template.getDockerCompose();
                rancherCompose = template.getRancherCompose();
            }
        }

        Map<Object, Object> data = CollectionUtils.asMap(
                (Object)STACK.EXTERNAL_ID, externalId,
                STACK.ACCOUNT_ID, accountId,
                STACK.NAME, catalogTemplate.getName(),
                STACK.DESCRIPTION, catalogTemplate.getDescription(),
                STACK.SYSTEM, true,
                ServiceConstants.STACK_FIELD_DOCKER_COMPOSE, dockerCompose,
                ServiceConstants.STACK_FIELD_RANCHER_COMPOSE, rancherCompose,
                ServiceConstants.STACK_FIELD_ENVIRONMENT, catalogTemplate.getAnswers(),
                ServiceConstants.STACK_FIELD_BINDING, catalogTemplate.getBinding());
        return resourceDao.createAndSchedule(Stack.class, objectManager.convertToPropertiesFor(Stack.class, data));
    }

    protected TemplateCollection getTemplates(String url) throws IOException {
        return Request.Get(url).execute().handleResponse(new ResponseHandler<TemplateCollection>() {
            @Override
            public TemplateCollection handleResponse(HttpResponse response) throws ClientProtocolException, IOException {
                if (response.getStatusLine().getStatusCode() != 200) {
                    return null;
                }
                return jsonMapper.readValue(response.getEntity().getContent(), TemplateCollection.class);
            }
        });
    }

    protected void refresh() throws IOException {
        if (firstCall) {
            String url = CATALOG_RESOURCE_URL.get();
            if (url.endsWith("/")) {
                url = url.substring(0, url.length() - 1);
            }
            Request.Post(String.format("%s?refresh&action=refresh", url)).execute().handleResponse((resp) -> {
                int status = resp.getStatusLine().getStatusCode();
                if (status >= 400) {
                    log.warn("Skipping catalog refresh because it failed. Response code: {}, reason: {}", status, resp.getStatusLine().getReasonPhrase());
                }
                return null;
            });
            firstCall = false;
        }
    }

    @Override
    public Map<String, Map<Object, Object>> getTemplates(List<ProjectTemplate> installed) throws IOException {
        refresh();

        Map<String, Map<Object, Object>> result = new HashMap<>();

        StringBuilder catalogTemplateUrl = new StringBuilder(CATALOG_RESOURCE_URL.get());
        appendVersionCheck(catalogTemplateUrl);
        if (catalogTemplateUrl.toString().contains("?")) {
            catalogTemplateUrl.append("&");
        } else {
            catalogTemplateUrl.append("?");
        }
        catalogTemplateUrl.append("templateBase_eq=project");

        TemplateCollection collection = getTemplates(catalogTemplateUrl.toString());

        Yaml yaml = new Yaml();
        if (collection.getData() != null) {
            for (Template template : collection.getData()) {
                if (template.getVersionLinks() == null) {
                    continue;
                }
                String url = template.getVersionLinks().get(template.getDefaultVersion());
                if (url == null) {
                    continue;
                }

                template = getTemplateAtURL(url);
                if (template == null || template.getFiles() == null) {
                    continue;
                }
                String file = template.getFiles().get("project.yml");
                if (file == null) {
                    continue;
                }

                @SuppressWarnings("unchecked")
                Map<Object, Object> project = yaml.loadAs(file, Map.class);
                result.put("catalog://" + template.getId(), project);
            }
        }

        return result;
    }

    @Override
    public Map<String, String> latestInfraTemplates() throws IOException {
        refresh();

        Map<String, String> result = new HashMap<>();
        StringBuilder catalogTemplateUrl = new StringBuilder(CATALOG_RESOURCE_URL.get());
        appendVersionCheck(catalogTemplateUrl);
        if (catalogTemplateUrl.indexOf("?") == -1) {
            catalogTemplateUrl.append("?");
        } else {
            catalogTemplateUrl.append("&");
        }
        catalogTemplateUrl.append("templateBase_eq=infra");

        TemplateCollection collection = getTemplates(catalogTemplateUrl.toString());
        for (Template template : collection.getData()) {
            if (!"library".equals(template.getCatalogId())) {
                continue;
            }
            String externalId = getDefaultOrLatestTemplateExternalId(template);
            if (StringUtils.isNotBlank(externalId)) {
                result.put(template.getId(), externalId);
            }
        }

        return result;
    }

    @Override
    public String getTemplateIdFromExternalId(String externalId) {
        if (StringUtils.isBlank(externalId)) {
            return null;
        }

        String templateId = StringUtils.removeStart(externalId, "catalog://");
        String[] parts = StringUtils.split(templateId, ":", 3);
        if (parts.length < 3) {
            return null;
        }

        return String.format("%s:%s", parts[0], parts[1]);
    }
}
