package at.procon.dip.domain.ted.service;

import at.procon.dip.runtime.condition.ConditionalOnRuntimeMode;
import at.procon.dip.runtime.config.RuntimeMode;
import at.procon.dip.ingestion.config.TedPackageDownloadProperties;
import at.procon.ted.model.entity.TedDailyPackage;
import at.procon.ted.repository.TedDailyPackageRepository;
import java.time.Duration;
import java.time.LocalDate;
import java.time.OffsetDateTime;
import java.time.Year;
import java.time.ZoneOffset;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

/**
 * NEW-runtime implementation of TED package sequencing.
 * <p>
 * This reuses the same decision rules as the legacy TED package downloader:
 * <ul>
 *   <li>current year forward crawling first</li>
 *   <li>gap filling by walking backward to package 1</li>
 *   <li>NOT_FOUND retry handling with current-year indefinite retry support</li>
 *   <li>previous-year grace period before a tail NOT_FOUND becomes final</li>
 * </ul>
 */
@Service
@ConditionalOnRuntimeMode(RuntimeMode.NEW)
@RequiredArgsConstructor
@Slf4j
public class DefaultTedPackageSequenceService implements TedPackageSequenceService {

    private final TedPackageDownloadProperties properties;
    private final TedDailyPackageRepository packageRepository;

    @Override
    public PackageInfo getNextPackageToDownload() {
        int currentYear = Year.now().getValue();

        log.debug("Determining next TED package to download for NEW runtime (current year: {})", currentYear);

        // 1) Current year forward crawling first (newest data first)
        PackageInfo nextInCurrentYear = getNextForwardPackage(currentYear);
        if (nextInCurrentYear != null) {
            log.info("Next TED package: {} (current year {} forward)", nextInCurrentYear.identifier(), currentYear);
            return nextInCurrentYear;
        }

        // 2) Walk all years backward and fill gaps / continue unfinished years
        for (int year = currentYear; year >= properties.getStartYear(); year--) {
            PackageInfo gapFiller = getGapFillerPackage(year);
            if (gapFiller != null) {
                log.info("Next TED package: {} (filling gap in year {})", gapFiller.identifier(), year);
                return gapFiller;
            }

            if (!isYearComplete(year)) {
                PackageInfo forwardPackage = getNextForwardPackage(year);
                if (forwardPackage != null) {
                    log.info("Next TED package: {} (continuing year {})", forwardPackage.identifier(), year);
                    return forwardPackage;
                }
            } else {
                log.debug("TED package year {} is complete", year);
            }
        }

        // 3) Open a new older year if possible
        int oldestYear = getOldestYearWithData();
        if (oldestYear > properties.getStartYear()) {
            int previousYear = oldestYear - 1;
            if (previousYear >= properties.getStartYear()) {
                PackageInfo first = new PackageInfo(previousYear, 1);
                log.info("Next TED package: {} (opening year {})", first.identifier(), previousYear);
                return first;
            }
        }

        log.info("All TED package years from {} to {} appear complete - nothing to download",
                properties.getStartYear(), currentYear);
        return null;
    }

    private PackageInfo getNextForwardPackage(int year) {
        Optional<TedDailyPackage> latest = packageRepository.findLatestByYear(year);

        if (latest.isEmpty()) {
            return new PackageInfo(year, 1);
        }

        TedDailyPackage latestPackage = latest.get();

        if (latestPackage.getDownloadStatus() == TedDailyPackage.DownloadStatus.NOT_FOUND) {
            if (shouldRetryNotFoundPackage(latestPackage)) {
                return new PackageInfo(year, latestPackage.getSerialNumber());
            }

            if (isNotFoundRetryableForYear(latestPackage)) {
                log.debug("Year {} still inside NOT_FOUND retry window for package {} until {}",
                        year, latestPackage.getPackageIdentifier(), calculateNextRetryAt(latestPackage));
                return null;
            }

            log.debug("Year {} finalized after grace period at tail package {}", year, latestPackage.getPackageIdentifier());
            return null;
        }

        return new PackageInfo(year, latestPackage.getSerialNumber() + 1);
    }

    private PackageInfo getGapFillerPackage(int year) {
        Optional<TedDailyPackage> first = packageRepository.findFirstByYear(year);

        if (first.isEmpty()) {
            return null;
        }

        int minSerial = first.get().getSerialNumber();
        if (minSerial <= 1) {
            return null;
        }

        return new PackageInfo(year, minSerial - 1);
    }

    private boolean isYearComplete(int year) {
        Optional<TedDailyPackage> first = packageRepository.findFirstByYear(year);
        Optional<TedDailyPackage> latest = packageRepository.findLatestByYear(year);

        if (first.isEmpty() || latest.isEmpty()) {
            return false;
        }

        if (first.get().getSerialNumber() != 1) {
            return false;
        }

        TedDailyPackage latestPackage = latest.get();
        return latestPackage.getDownloadStatus() == TedDailyPackage.DownloadStatus.NOT_FOUND
                && !isNotFoundRetryableForYear(latestPackage);
    }

    private boolean shouldRetryNotFoundPackage(TedDailyPackage pkg) {
        if (!isNotFoundRetryableForYear(pkg)) {
            return false;
        }

        OffsetDateTime nextRetryAt = calculateNextRetryAt(pkg);
        return !nextRetryAt.isAfter(OffsetDateTime.now());
    }

    private boolean isNotFoundRetryableForYear(TedDailyPackage pkg) {
        int currentYear = Year.now().getValue();
        int packageYear = pkg.getYear() != null ? pkg.getYear() : currentYear;

        if (packageYear >= currentYear) {
            return properties.isRetryCurrentYearNotFoundIndefinitely();
        }

        return OffsetDateTime.now().isBefore(getYearRetryGraceDeadline(packageYear));
    }

    private OffsetDateTime calculateNextRetryAt(TedDailyPackage pkg) {
        OffsetDateTime lastAttemptAt = pkg.getUpdatedAt() != null
                ? pkg.getUpdatedAt()
                : (pkg.getCreatedAt() != null ? pkg.getCreatedAt() : OffsetDateTime.now());

        return lastAttemptAt.plus(Duration.ofMillis(properties.getNotFoundRetryInterval()));
    }

    private OffsetDateTime getYearRetryGraceDeadline(int year) {
        return LocalDate.of(year + 1, 1, 1)
                .atStartOfDay()
                .atOffset(ZoneOffset.UTC)
                .plusDays(properties.getPreviousYearGracePeriodDays());
    }

    private int getOldestYearWithData() {
        int currentYear = Year.now().getValue();
        for (int year = properties.getStartYear(); year <= currentYear; year++) {
            if (packageRepository.findLatestByYear(year).isPresent()) {
                return year;
            }
        }
        return currentYear;
    }
}
