package com.r35157.nenjim.service.journal.model;

import com.r35157.libs.valuetypes.basic.SemanticVersion;
import com.r35157.nenjim.service.journal.valuetypes.ContentDigest;
import com.r35157.nenjim.service.journal.valuetypes.JournalVersion;

import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;

/** One immutable, complete Journal revision for exactly one artifact. */
public record Journal(
        int formatVersion,
        JournalVersion journalVersion,
        Optional<ContentDigest> previousJournalDigest,
        JournalMetadata metadata,
        JournalPolicy policy,
        Map<SemanticVersion, ArtifactRelease> releases) {
    public Journal {
        if (formatVersion != 1) {
            throw new IllegalArgumentException("Unsupported Journal format version: " + formatVersion);
        }
        Objects.requireNonNull(journalVersion, "journalVersion");
        previousJournalDigest = Objects.requireNonNull(previousJournalDigest, "previousJournalDigest");
        previousJournalDigest.ifPresent(digest -> {
            if (!ContentDigest.SHA256.equals(digest.scheme())) {
                throw new IllegalArgumentException("Previous Journal digest must use SHA-256");
            }
        });
        Objects.requireNonNull(metadata, "metadata");
        Objects.requireNonNull(policy, "policy");
        Objects.requireNonNull(releases, "releases");
        if (releases.isEmpty()) {
            throw new IllegalArgumentException("A Journal requires at least one artifact release");
        }

        LinkedHashMap<SemanticVersion, ArtifactRelease> copy = new LinkedHashMap<>();
        releases.forEach((version, release) -> {
            Objects.requireNonNull(version, "release version");
            Objects.requireNonNull(release, "release");
            if (!version.equals(release.version())) {
                throw new IllegalArgumentException("Release map key does not match release version: " + version);
            }
            if (copy.put(version, release) != null) {
                throw new IllegalArgumentException("Duplicate release version: " + version);
            }
        });
        validateRecommendations(policy, copy);
        releases = Collections.unmodifiableMap(copy);
    }

    public Optional<ArtifactRelease> release(SemanticVersion version) {
        return Optional.ofNullable(releases.get(Objects.requireNonNull(version, "version")));
    }

    private static void validateRecommendations(
            JournalPolicy policy,
            Map<SemanticVersion, ArtifactRelease> releases) {
        for (PolicyRule recommendation : policy.recommended()) {
            SemanticVersion version = recommendation.versions().expressions().getFirst()
                    .exactVersion()
                    .orElseThrow(() -> new IllegalArgumentException(
                            "RECOMMENDED must identify exactly one exact major.minor.patch release"));
            if (!releases.containsKey(version)) {
                throw new IllegalArgumentException(
                        "RECOMMENDED release " + version
                                + " does not exist in the same Journal snapshot");
            }
        }
    }
}
