package p.studio.compiler.utilities;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.experimental.UtilityClass;
import org.apache.commons.lang3.StringUtils;
import p.studio.compiler.FrontendRegistryService;
import p.studio.compiler.dtos.PrometeuManifestDTO;
import p.studio.compiler.messages.BuildingIssueSink;
import p.studio.compiler.models.PrometeuManifest;
import p.studio.compiler.workspaces.phases.DependencyResolver;
import p.studio.utilities.structures.ReadOnlyCollection;
import p.studio.utilities.structures.ReadOnlyList;

import java.io.IOException;
import java.nio.file.Path;
import java.util.Optional;
import java.util.OptionalInt;

@UtilityClass
public final class PrometeuManifestUtils {

    private final static ObjectMapper MAPPER = new ObjectMapper();

    static PrometeuManifestDTO read(Path manifestPath) {
        try {
            return MAPPER.readValue(manifestPath.toFile(), PrometeuManifestDTO.class);
        } catch (IOException e) {
            throw new IllegalStateException("failed to read manifest " + manifestPath, e);
        }
    }

    public static Optional<PrometeuManifest> buildManifest(
            final Path mainProjectRootPathCanon,
            final Path manifestPathCanon,
            final BuildingIssueSink issues) {
        final var issuesLocal = BuildingIssueSink.empty();
        final PrometeuManifestDTO dto = read(manifestPathCanon);
        if (StringUtils.isBlank(dto.name())) {
            issuesLocal.report(builder -> builder
                    .error(true)
                    .message("[DEPS]: manifest missing 'name': " + manifestPathCanon));
        }
        if (StringUtils.isBlank(dto.version())) {
            issuesLocal.report(builder -> builder
                    .error(true)
                    .message("[DEPS]: manifest missing 'version': " + manifestPathCanon));
        }
        final var stdlibMaybe = parseStdlib(dto.stdlib(), manifestPathCanon, issuesLocal);
        final var language = StringUtils.isBlank(dto.language())
                ? FrontendRegistryService.getDefaultFrontendSpec().getLanguageId()
                : dto.language();
        final var dependencies = DependencyResolver.resolveDependencies(mainProjectRootPathCanon, dto.dependencies(), issuesLocal);
        if (ReadOnlyCollection.isNotEmpty(issuesLocal)) {
            issues.merge(issuesLocal);
            return Optional.empty();
        }
        return Optional.of(new PrometeuManifest(dto.name(), dto.version(), language, stdlibMaybe.getAsInt(), ReadOnlyList.wrap(dependencies)));
    }

    private static OptionalInt parseStdlib(
            final String stdlibStr,
            final Path manifestPathCanon,
            final BuildingIssueSink issues) {
        if (StringUtils.isBlank(stdlibStr)) {
            issues.report(builder -> builder
                    .error(true)
                    .message("[DEPS]: manifest missing 'stdlib': " + manifestPathCanon));
            return OptionalInt.empty();
        }
        final int stdlib;
        try {
            stdlib = Integer.parseInt(stdlibStr);
        } catch (NumberFormatException e) {
            issues.report(builder -> builder
                    .error(true)
                    .message("[DEPS]: invalid 'stdlib' major version \"" + stdlibStr + "\": " + manifestPathCanon));
            return OptionalInt.empty();
        }
        if (stdlib <= 0 || !stdlibStr.equals(Integer.toString(stdlib))) {
            issues.report(builder -> builder
                    .error(true)
                    .message("[DEPS]: invalid 'stdlib' major version \"" + stdlibStr + "\": " + manifestPathCanon));
            return OptionalInt.empty();
        }
        return OptionalInt.of(stdlib);
    }
}
