package com.magistr.app.config.auth;

import com.magistr.app.config.tenant.TenantContext;
import com.magistr.app.config.tenant.TenantRoutingDataSource;
import com.magistr.app.repository.AuthRefreshTokenRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;

/**
 * Периодически очищает старые refresh-сессии отдельно в каждой активной tenant-БД.
 */
@Component
public class RefreshTokenCleanupJob {

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

    private final TenantRoutingDataSource routingDataSource;
    private final AuthRefreshTokenRepository repository;
    private final boolean enabled;
    private final Duration retention;
    private final int batchSize;
    private final int maxBatchesPerTenant;
    private final Clock clock;
    private final AtomicBoolean cleanupInProgress = new AtomicBoolean(false);

    @Autowired
    public RefreshTokenCleanupJob(
            TenantRoutingDataSource routingDataSource,
            AuthRefreshTokenRepository repository,
            @Value("${app.jwt.refresh-cleanup.enabled:true}") boolean enabled,
            @Value("${app.jwt.refresh-cleanup.retention:30d}") Duration retention,
            @Value("${app.jwt.refresh-cleanup.batch-size:500}") int batchSize,
            @Value("${app.jwt.refresh-cleanup.max-batches-per-tenant:20}") int maxBatchesPerTenant) {
        this(
                routingDataSource,
                repository,
                enabled,
                retention,
                batchSize,
                maxBatchesPerTenant,
                Clock.systemUTC()
        );
    }

    RefreshTokenCleanupJob(TenantRoutingDataSource routingDataSource,
                           AuthRefreshTokenRepository repository,
                           boolean enabled,
                           Duration retention,
                           int batchSize,
                           int maxBatchesPerTenant,
                           Clock clock) {
        this.routingDataSource = Objects.requireNonNull(
                routingDataSource,
                "Маршрутизатор tenant-БД обязателен"
        );
        this.repository = Objects.requireNonNull(repository, "Репозиторий refresh-токенов обязателен");
        if (retention == null || retention.isZero() || retention.isNegative()) {
            throw new IllegalArgumentException("Срок хранения refresh-сессий должен быть положительным");
        }
        if (batchSize < 1 || batchSize > 10_000) {
            throw new IllegalArgumentException("Размер пачки очистки должен быть от 1 до 10000");
        }
        if (maxBatchesPerTenant < 1 || maxBatchesPerTenant > 1_000) {
            throw new IllegalArgumentException("Число пачек очистки должно быть от 1 до 1000");
        }
        this.enabled = enabled;
        this.retention = retention;
        this.batchSize = batchSize;
        this.maxBatchesPerTenant = maxBatchesPerTenant;
        this.clock = Objects.requireNonNull(clock, "Часы очистки обязательны");
    }

    @Scheduled(
            fixedDelayString = "${app.jwt.refresh-cleanup.interval-ms:3600000}",
            initialDelayString = "${app.jwt.refresh-cleanup.initial-delay-ms:60000}"
    )
    public void cleanupScheduled() {
        if (enabled) {
            cleanupNow();
        }
    }

    /**
     * Выполняет один ограниченный проход. Локальный guard не допускает наложения запусков
     * внутри pod, а SQL SKIP LOCKED обеспечивает безопасную совместную работу нескольких pod.
     */
    public CleanupSummary cleanupNow() {
        if (!cleanupInProgress.compareAndSet(false, true)) {
            return new CleanupSummary(0, 0, 0, true);
        }

        String previousTenant = TenantContext.getCurrentTenant();
        try {
            TenantContext.clear();
            Map<String, ?> tenantSnapshot = routingDataSource.snapshotTenantConfigs();
            Instant cutoff = clock.instant().minus(retention);
            int deletedTotal = 0;
            int failedTenants = 0;

            for (String tenant : tenantSnapshot.keySet()) {
                TenantContext.setCurrentTenant(tenant);
                try {
                    deletedTotal += cleanupTenant(tenant, cutoff);
                } catch (RuntimeException cleanupFailure) {
                    failedTenants++;
                    log.warn("Очистка refresh-сессий tenant-БД '{}' завершилась ошибкой: errorType={}",
                            tenant, cleanupFailure.getClass().getSimpleName());
                    log.debug("Технические детали очистки refresh-сессий", cleanupFailure);
                } finally {
                    TenantContext.clear();
                }
            }

            if (deletedTotal > 0) {
                log.info("Очистка refresh-сессий завершена: tenantCount={}, deletedCount={}, failedCount={}",
                        tenantSnapshot.size(), deletedTotal, failedTenants);
            }
            return new CleanupSummary(tenantSnapshot.size(), deletedTotal, failedTenants, false);
        } finally {
            restoreTenant(previousTenant);
            cleanupInProgress.set(false);
        }
    }

    private int cleanupTenant(String tenant, Instant cutoff) {
        int deletedTotal = 0;
        for (int batch = 0; batch < maxBatchesPerTenant; batch++) {
            int deleted = repository.deleteCleanupBatch(cutoff, batchSize);
            deletedTotal += deleted;
            if (deleted < batchSize) {
                return deletedTotal;
            }
        }
        log.warn("Очистка refresh-сессий tenant-БД '{}' достигла лимита пачек за один проход",
                tenant);
        return deletedTotal;
    }

    private void restoreTenant(String previousTenant) {
        if (previousTenant == null || previousTenant.isBlank()) {
            TenantContext.clear();
        } else {
            TenantContext.setCurrentTenant(previousTenant);
        }
    }

    public record CleanupSummary(int tenantCount,
                                 int deletedCount,
                                 int failedTenantCount,
                                 boolean skippedBecauseAlreadyRunning) {
    }
}
