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.AuthLoginAttemptAuditRepository;
import com.magistr.app.repository.AuthLoginRateLimitRepository;
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;

/** Очищает старый аудит входа и неактивные счётчики отдельно в каждой tenant-БД. */
@Component
public class LoginAttemptAuditCleanupJob {

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

    private final TenantRoutingDataSource routingDataSource;
    private final AuthLoginAttemptAuditRepository auditRepository;
    private final AuthLoginRateLimitRepository rateLimitRepository;
    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 LoginAttemptAuditCleanupJob(
            TenantRoutingDataSource routingDataSource,
            AuthLoginAttemptAuditRepository auditRepository,
            AuthLoginRateLimitRepository rateLimitRepository,
            @Value("${app.login-security.audit-cleanup.enabled:true}") boolean enabled,
            @Value("${app.login-security.audit-cleanup.retention:90d}") Duration retention,
            @Value("${app.login-security.audit-cleanup.batch-size:500}") int batchSize,
            @Value("${app.login-security.audit-cleanup.max-batches-per-tenant:20}") int maxBatchesPerTenant) {
        this(routingDataSource, auditRepository, rateLimitRepository, enabled, retention,
                batchSize, maxBatchesPerTenant, Clock.systemUTC());
    }

    LoginAttemptAuditCleanupJob(TenantRoutingDataSource routingDataSource,
                                AuthLoginAttemptAuditRepository auditRepository,
                                AuthLoginRateLimitRepository rateLimitRepository,
                                boolean enabled,
                                Duration retention,
                                int batchSize,
                                int maxBatchesPerTenant,
                                Clock clock) {
        this.routingDataSource = Objects.requireNonNull(routingDataSource);
        this.auditRepository = Objects.requireNonNull(auditRepository);
        this.rateLimitRepository = Objects.requireNonNull(rateLimitRepository);
        if (retention == null || retention.isZero() || retention.isNegative()) {
            throw new IllegalArgumentException("Срок хранения аудита входа должен быть положительным");
        }
        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.login-security.audit-cleanup.interval-ms:86400000}",
            initialDelayString = "${app.login-security.audit-cleanup.initial-delay-ms:120000}"
    )
    public void cleanupScheduled() {
        if (enabled) {
            cleanupNow();
        }
    }

    public CleanupSummary cleanupNow() {
        if (!cleanupInProgress.compareAndSet(false, true)) {
            return new CleanupSummary(0, 0, 0, 0, true);
        }
        String previousTenant = TenantContext.getCurrentTenant();
        try {
            TenantContext.clear();
            Map<String, ?> tenants = routingDataSource.snapshotTenantConfigs();
            Instant now = clock.instant();
            Instant cutoff = now.minus(retention);
            int deletedAudits = 0;
            int deletedLimits = 0;
            int failedTenants = 0;
            for (String tenant : tenants.keySet()) {
                TenantContext.setCurrentTenant(tenant);
                try {
                    deletedAudits += cleanupAudit(tenant, cutoff);
                    deletedLimits += cleanupLimits(tenant, cutoff, now);
                } catch (RuntimeException failure) {
                    failedTenants++;
                    log.warn("Очистка аудита входа tenant-БД '{}' завершилась ошибкой: errorType={}",
                            tenant, failure.getClass().getSimpleName());
                    log.debug("Технические детали очистки аудита входа", failure);
                } finally {
                    TenantContext.clear();
                }
            }
            if (deletedAudits + deletedLimits > 0) {
                log.info("Очистка аудита входа завершена: tenantCount={}, auditDeleted={}, rateLimitDeleted={}, failedCount={}",
                        tenants.size(), deletedAudits, deletedLimits, failedTenants);
            }
            return new CleanupSummary(
                    tenants.size(), deletedAudits, deletedLimits, failedTenants, false
            );
        } finally {
            restoreTenant(previousTenant);
            cleanupInProgress.set(false);
        }
    }

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

    private int cleanupLimits(String tenant, Instant cutoff, Instant now) {
        int deletedTotal = 0;
        for (int batch = 0; batch < maxBatchesPerTenant; batch++) {
            int deleted = rateLimitRepository.deleteStaleBatch(cutoff, now, batchSize);
            deletedTotal += deleted;
            if (deleted < batchSize) {
                return deletedTotal;
            }
        }
        log.warn("Очистка счётчиков входа tenant-БД '{}' достигла лимита пачек", tenant);
        return deletedTotal;
    }

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

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