package com.atlassian.bitbucket.mesh.app;

import com.atlassian.bitbucket.mesh.util.Version;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.time.Instant;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

/**
 * Parses and provides access to build properties, such as version and commit information.
 */
public class BuildInfo {

    private static final Logger log = LoggerFactory.getLogger(BuildInfo.class);

    private final String buildNumber;
    private final OffsetDateTime buildTimestamp;
    private final OffsetDateTime commitTimestamp;
    private final BuildProperties properties;

    public BuildInfo(BuildProperties properties) {
        this.properties = properties;

        buildNumber = calculateBuildNumber(properties.getVersion());
        buildTimestamp = parseBuildTimestamp(properties.getTimestamp(), properties.getTimestampFormat());
        commitTimestamp = parseCommitTimestamp(properties.getCommit().getTimestamp(), buildTimestamp.getOffset());
    }

    @Nonnull
    public String getBuildNumber() {
        return buildNumber;
    }

    @Nonnull
    public String getBuildVersion() {
        return properties.getVersion();
    }

    @Nonnull
    public OffsetDateTime getBuildTimestamp() {
        return buildTimestamp;
    }

    @Nonnull
    public String getCommitHash() {
        return properties.getCommit().getHash();
    }

    @Nonnull
    public String getDisplayCommitHash() {
        return StringUtils.substring(getCommitHash(), 0, 7);
    }

    @Nullable
    public OffsetDateTime getCommitTimestamp() {
        return commitTimestamp;
    }

    /**
     * Checks if the provided build number is compatible with the current one
     *
     * @param build the build number to compare with
     * @return {@code true} if the two build numbers are compatible, {@code false} otherwise
     */
    public boolean isCompatible(@Nonnull String build) {
        return build.startsWith(getMajorVersion()) && build.length() == buildNumber.length();
    }

    private static String calculateBuildNumber(String buildVersion) {
        Version version = new Version(buildVersion);
        long major = version.getMajor();
        long minor = version.getMinor();
        long patch = version.getPatch();

        //This format allows for 1000 releases at any given level; 1.0.0 to 1.0.999 before 1.1.0, etc.
        long full = ((major * 1000L) + minor) * 1000L + patch;
        return String.valueOf(full);
    }

    private static OffsetDateTime parseBuildTimestamp(String timestamp, String format) {
        try {
            return OffsetDateTime.parse(timestamp, DateTimeFormatter.ofPattern(format, Locale.ROOT));
        } catch (RuntimeException e) {
            log.warn("Failed to parse build timestamp [{} ({})], using current time instead", timestamp, format);

            return OffsetDateTime.now();
        }
    }

    private static OffsetDateTime parseCommitTimestamp(String timestamp, ZoneOffset offset) {
        try {
            return OffsetDateTime.ofInstant(Instant.ofEpochMilli(Long.parseLong(timestamp)), offset);
        } catch (NumberFormatException e) {
            log.debug("Failed to parse commit timestamp [{}]", timestamp);

            return null;
        }
    }

    private String getMajorVersion() {
        return String.valueOf(new Version(getBuildVersion()).getMajor());
    }
}
