package com.cameleer.server.core.runtime;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.security.MessageDigest;
import java.util.HexFormat;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.regex.Pattern;

public class AppService {
    private static final Logger log = LoggerFactory.getLogger(AppService.class);

    /** Slug rules mirror {@link EnvironmentService#SLUG_PATTERN}: lowercase letters, digits, hyphens; 1–64 chars; starts with alnum. Immutable after creation. */
    private static final Pattern SLUG_PATTERN = Pattern.compile("^[a-z0-9][a-z0-9-]{0,63}$");

    private final AppRepository appRepo;
    private final AppVersionRepository versionRepo;
    private final String jarStoragePath;
    private final CreateGuard createGuard;

    public AppService(AppRepository appRepo, AppVersionRepository versionRepo, String jarStoragePath) {
        this(appRepo, versionRepo, jarStoragePath, CreateGuard.NOOP);
    }

    public AppService(AppRepository appRepo, AppVersionRepository versionRepo, String jarStoragePath,
                      CreateGuard createGuard) {
        this.appRepo = appRepo;
        this.versionRepo = versionRepo;
        this.jarStoragePath = jarStoragePath;
        this.createGuard = createGuard;
    }

    public List<App> listAll() { return appRepo.findAll(); }
    public List<App> listByEnvironment(UUID environmentId) { return appRepo.findByEnvironmentId(environmentId); }
    public App getById(UUID id) { return appRepo.findById(id).orElseThrow(() -> new IllegalArgumentException("App not found: " + id)); }
    public App getBySlug(String slug) { return appRepo.findBySlug(slug).orElseThrow(() -> new IllegalArgumentException("App not found: " + slug)); }
    public App getByEnvironmentAndSlug(UUID environmentId, String slug) {
        return appRepo.findByEnvironmentIdAndSlug(environmentId, slug)
                .orElseThrow(() -> new IllegalArgumentException("App not found in environment: " + slug));
    }
    public List<AppVersion> listVersions(UUID appId) { return versionRepo.findByAppId(appId); }

    public AppVersion getVersion(UUID versionId) {
        return versionRepo.findById(versionId)
                .orElseThrow(() -> new IllegalArgumentException("AppVersion not found: " + versionId));
    }

    public void updateContainerConfig(UUID id, Map<String, Object> containerConfig) {
        getById(id); // verify exists
        appRepo.updateContainerConfig(id, containerConfig);
    }

    public UUID createApp(UUID environmentId, String slug, String displayName) {
        if (slug == null || !SLUG_PATTERN.matcher(slug).matches()) {
            throw new IllegalArgumentException(
                    "Invalid app slug: must match ^[a-z0-9][a-z0-9-]{0,63}$ (lowercase letters, digits, hyphens)");
        }
        createGuard.check(appRepo.count());
        if (appRepo.findByEnvironmentIdAndSlug(environmentId, slug).isPresent()) {
            throw new IllegalArgumentException("App with slug '" + slug + "' already exists in this environment");
        }
        return appRepo.create(environmentId, slug, displayName);
    }

    public AppVersion uploadJar(UUID appId, String filename, InputStream jarData, long size) throws IOException {
        getById(appId); // verify app exists
        int nextVersion = versionRepo.findMaxVersion(appId) + 1;

        // Store JAR: {jarStoragePath}/{appId}/v{version}/app.jar
        Path versionDir = Path.of(jarStoragePath, appId.toString(), "v" + nextVersion);
        Files.createDirectories(versionDir);
        Path jarFile = versionDir.resolve("app.jar");

        MessageDigest digest;
        try { digest = MessageDigest.getInstance("SHA-256"); }
        catch (Exception e) { throw new RuntimeException(e); }

        try (InputStream in = jarData) {
            byte[] buffer = new byte[8192];
            int bytesRead;
            try (var out = Files.newOutputStream(jarFile)) {
                while ((bytesRead = in.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesRead);
                    digest.update(buffer, 0, bytesRead);
                }
            }
        }

        String checksum = HexFormat.of().formatHex(digest.digest());
        UUID versionId = versionRepo.create(appId, nextVersion, jarFile.toString(), checksum, filename, size);

        // Detect runtime type from the saved JAR
        RuntimeDetector.DetectionResult detection = RuntimeDetector.detect(jarFile);
        if (detection.runtimeType() != null) {
            versionRepo.updateDetectedRuntime(versionId, detection.runtimeType().toConfigValue(), detection.mainClass());
            log.info("Uploaded JAR for app {}: version={}, size={}, sha256={}, detected={}",
                    appId, nextVersion, size, checksum, detection.runtimeType().toConfigValue());
        } else {
            log.info("Uploaded JAR for app {}: version={}, size={}, sha256={}, detected=unknown",
                    appId, nextVersion, size, checksum);
        }

        return versionRepo.findById(versionId).orElseThrow();
    }

    public String resolveJarPath(UUID appVersionId) {
        AppVersion version = versionRepo.findById(appVersionId)
                .orElseThrow(() -> new IllegalArgumentException("AppVersion not found: " + appVersionId));
        return version.jarPath();
    }

    public void deleteApp(UUID id) {
        appRepo.delete(id);
    }
}
