package io.cameleer.server.app.retention;

import io.cameleer.server.core.runtime.*;
import io.cameleer.server.core.storage.ArtifactStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;

/**
 * Nightly job that enforces JAR retention policies per environment.
 * For each app, keeps the N most recent versions (configured per environment)
 * and deletes older ones — unless they are currently deployed.
 */
@Component
public class JarRetentionJob {

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

    private final EnvironmentService environmentService;
    private final AppService appService;
    private final AppVersionRepository versionRepo;
    private final DeploymentRepository deploymentRepo;
    private final ArtifactStore store;

    public JarRetentionJob(EnvironmentService environmentService,
                           AppService appService,
                           AppVersionRepository versionRepo,
                           DeploymentRepository deploymentRepo,
                           ArtifactStore store) {
        this.environmentService = environmentService;
        this.appService = appService;
        this.versionRepo = versionRepo;
        this.deploymentRepo = deploymentRepo;
        this.store = store;
    }

    @Scheduled(cron = "0 0 3 * * *") // 03:00 every day
    public void cleanupOldVersions() {
        log.info("JAR retention job started");
        int totalDeleted = 0;

        for (Environment env : environmentService.listAll()) {
            Integer retentionCount = env.jarRetentionCount();
            if (retentionCount == null) {
                log.debug("Environment {} has unlimited retention, skipping", env.slug());
                continue;
            }
            for (App app : appService.listByEnvironment(env.id())) {
                totalDeleted += cleanupApp(app, retentionCount);
            }
        }

        log.info("JAR retention job completed — deleted {} versions", totalDeleted);
    }

    private int cleanupApp(App app, int retentionCount) {
        List<AppVersion> versions = versionRepo.findByAppId(app.id()); // ordered DESC by version
        if (versions.size() <= retentionCount) return 0;

        Set<UUID> deployedVersionIds = deploymentRepo.findByAppId(app.id()).stream()
                .map(Deployment::appVersionId).collect(Collectors.toSet());

        int deleted = 0;
        for (int i = retentionCount; i < versions.size(); i++) {
            AppVersion version = versions.get(i);
            if (deployedVersionIds.contains(version.id())) {
                log.debug("Skipping deployed version v{} of app {} ({})",
                        version.version(), app.slug(), version.id());
                continue;
            }
            try {
                store.delete(appService.coordinatesFor(version));
            } catch (IOException e) {
                // Don't abort the sweep for one app's IO error — match the legacy
                // log-and-continue behavior. The DB row still gets cleaned up since
                // the JAR is no longer pointed at by anything (FilesystemArtifactStore.delete
                // already handles the racy parent-sweep gracefully).
                log.warn("Failed to delete artifact for version v{} of app {} ({})",
                        version.version(), app.slug(), version.id(), e);
            }
            versionRepo.delete(version.id());
            deleted++;
            log.info("Deleted version v{} of app {} ({})",
                    version.version(), app.slug(), version.id());
        }
        return deleted;
    }
}
