package at.procon.dip.migration.audit.service;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;

import at.procon.dip.migration.audit.config.LegacyTedAuditProperties;
import at.procon.dip.migration.audit.entity.LegacyTedAuditFinding;
import at.procon.dip.migration.audit.entity.LegacyTedAuditFindingType;
import at.procon.dip.migration.audit.entity.LegacyTedAuditRun;
import at.procon.dip.migration.audit.entity.LegacyTedAuditRunStatus;
import at.procon.dip.migration.audit.repository.LegacyTedAuditFindingRepository;
import at.procon.dip.migration.audit.repository.LegacyTedAuditRunRepository;
import at.procon.ted.model.entity.NoticeType;
import at.procon.ted.model.entity.ProcurementDocument;
import at.procon.ted.model.entity.TedDailyPackage;
import at.procon.ted.repository.ProcurementDocumentRepository;
import at.procon.ted.repository.TedDailyPackageRepository;
import java.time.OffsetDateTime;
import java.time.Year;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.jdbc.core.JdbcTemplate;

@ExtendWith(MockitoExtension.class)
class LegacyTedAuditServiceTest {

    @Mock
    private TedDailyPackageRepository tedDailyPackageRepository;
    @Mock
    private ProcurementDocumentRepository procurementDocumentRepository;
    @Mock
    private LegacyTedAuditRunRepository runRepository;
    @Mock
    private LegacyTedAuditFindingRepository findingRepository;
    @Mock
    private JdbcTemplate jdbcTemplate;

    private LegacyTedAuditService service;
    private List<LegacyTedAuditFinding> persistedFindings;

    @BeforeEach
    void setUp() {
        LegacyTedAuditProperties properties = new LegacyTedAuditProperties();
        properties.setEnabled(true);
        properties.setPageSize(50);
        properties.setMaxFindingsPerRun(100);
        properties.setMaxDuplicateSamples(10);

        service = new LegacyTedAuditService(
                properties,
                tedDailyPackageRepository,
                procurementDocumentRepository,
                runRepository,
                findingRepository,
                jdbcTemplate
        );

        persistedFindings = new ArrayList<>();

        when(runRepository.save(any(LegacyTedAuditRun.class))).thenAnswer(invocation -> {
            LegacyTedAuditRun run = invocation.getArgument(0);
            if (run.getId() == null) {
                run.setId(UUID.randomUUID());
            }
            return run;
        });

        when(findingRepository.save(any(LegacyTedAuditFinding.class))).thenAnswer(invocation -> {
            LegacyTedAuditFinding finding = invocation.getArgument(0);
            if (finding.getId() == null) {
                finding.setId(UUID.randomUUID());
            }
            persistedFindings.add(finding);
            return finding;
        });

        when(procurementDocumentRepository.findAll(any(org.springframework.data.domain.Pageable.class)))
                .thenReturn(new PageImpl<>(List.of()));
    }

    @Test
    void executeAudit_should_record_package_sequence_gaps_and_incomplete_packages() {
        int currentYear = Year.now().getValue();

        when(tedDailyPackageRepository.findAll(any(org.springframework.data.domain.Sort.class))).thenReturn(List.of(
                TedDailyPackage.builder()
                        .packageIdentifier(formatPackageIdentifier(currentYear, 1))
                        .year(currentYear)
                        .serialNumber(1)
                        .downloadStatus(TedDailyPackage.DownloadStatus.COMPLETED)
                        .xmlFileCount(10)
                        .processedCount(10)
                        .failedCount(0)
                        .fileHash("hash-1")
                        .processedAt(OffsetDateTime.now())
                        .build(),
                TedDailyPackage.builder()
                        .packageIdentifier(formatPackageIdentifier(currentYear, 3))
                        .year(currentYear)
                        .serialNumber(3)
                        .downloadStatus(TedDailyPackage.DownloadStatus.COMPLETED)
                        .xmlFileCount(10)
                        .processedCount(9)
                        .failedCount(1)
                        .fileHash("hash-3")
                        .processedAt(OffsetDateTime.now())
                        .build(),
                TedDailyPackage.builder()
                        .packageIdentifier(formatPackageIdentifier(currentYear, 4))
                        .year(currentYear)
                        .serialNumber(4)
                        .downloadStatus(TedDailyPackage.DownloadStatus.FAILED)
                        .xmlFileCount(12)
                        .processedCount(0)
                        .failedCount(0)
                        .errorMessage("processing failed")
                        .build()
        ));

        LegacyTedAuditRun run = service.executeAudit(0);

        assertThat(run.getStatus()).isEqualTo(LegacyTedAuditRunStatus.COMPLETED);
        assertThat(run.getScannedPackages()).isEqualTo(3);
        assertThat(persistedFindings)
                .extracting(LegacyTedAuditFinding::getFindingType)
                .contains(LegacyTedAuditFindingType.PACKAGE_SEQUENCE_GAP,
                        LegacyTedAuditFindingType.PACKAGE_INCOMPLETE);

        assertThat(persistedFindings)
                .filteredOn(f -> f.getFindingType() == LegacyTedAuditFindingType.PACKAGE_SEQUENCE_GAP)
                .extracting(LegacyTedAuditFinding::getReferenceKey)
                .contains(formatPackageIdentifier(currentYear, 2));

        assertThat(persistedFindings)
                .filteredOn(f -> f.getFindingType() == LegacyTedAuditFindingType.PACKAGE_INCOMPLETE)
                .extracting(LegacyTedAuditFinding::getPackageIdentifier)
                .contains(formatPackageIdentifier(currentYear, 3), formatPackageIdentifier(currentYear, 4));
    }

    @Test
    void executeAudit_should_record_missing_years_inside_audited_interval() {
        int currentYear = Year.now().getValue();

        when(tedDailyPackageRepository.findAll(any(org.springframework.data.domain.Sort.class))).thenReturn(List.of(
                TedDailyPackage.builder()
                        .packageIdentifier(formatPackageIdentifier(currentYear - 2, 1))
                        .year(currentYear - 2)
                        .serialNumber(1)
                        .downloadStatus(TedDailyPackage.DownloadStatus.COMPLETED)
                        .xmlFileCount(1)
                        .processedCount(1)
                        .failedCount(0)
                        .fileHash("hash-a")
                        .processedAt(OffsetDateTime.now())
                        .build(),
                TedDailyPackage.builder()
                        .packageIdentifier(formatPackageIdentifier(currentYear, 1))
                        .year(currentYear)
                        .serialNumber(1)
                        .downloadStatus(TedDailyPackage.DownloadStatus.COMPLETED)
                        .xmlFileCount(1)
                        .processedCount(1)
                        .failedCount(0)
                        .fileHash("hash-b")
                        .processedAt(OffsetDateTime.now())
                        .build()
        ));

        LegacyTedAuditRun run = service.executeAudit(0);

        assertThat(run.getStatus()).isEqualTo(LegacyTedAuditRunStatus.COMPLETED);
        assertThat(persistedFindings)
                .filteredOn(f -> f.getFindingType() == LegacyTedAuditFindingType.PACKAGE_SEQUENCE_GAP)
                .extracting(LegacyTedAuditFinding::getReferenceKey)
                .contains("year:" + (currentYear - 1));
    }

    @Test
    void executeAudit_should_record_legacy_document_integrity_findings_only() {
        ProcurementDocument missingXml = ProcurementDocument.builder()
                .id(UUID.randomUUID())
                .documentHash("hash-1")
                .publicationId("2025/S 001-000001")
                .noticeType(NoticeType.CONTRACT_NOTICE)
                .xmlDocument(null)
                .textContent("hello")
                .build();

        ProcurementDocument missingTextAndPublicationId = ProcurementDocument.builder()
                .id(UUID.randomUUID())
                .documentHash("hash-2")
                .publicationId(null)
                .noticeType(NoticeType.CONTRACT_NOTICE)
                .xmlDocument("<xml/>")
                .textContent(null)
                .build();

        when(tedDailyPackageRepository.findAll(any(org.springframework.data.domain.Sort.class))).thenReturn(List.of());
        when(procurementDocumentRepository.findAll(any(org.springframework.data.domain.Pageable.class)))
                .thenReturn(pageOf(missingXml, missingTextAndPublicationId));

        LegacyTedAuditRun run = service.executeAudit(10);

        assertThat(run.getStatus()).isEqualTo(LegacyTedAuditRunStatus.COMPLETED);
        assertThat(run.getScannedLegacyDocuments()).isEqualTo(2);
        assertThat(persistedFindings)
                .extracting(LegacyTedAuditFinding::getFindingType)
                .contains(
                        LegacyTedAuditFindingType.LEGACY_DOCUMENT_MISSING_XML,
                        LegacyTedAuditFindingType.LEGACY_DOCUMENT_MISSING_TEXT,
                        LegacyTedAuditFindingType.LEGACY_DOCUMENT_MISSING_PUBLICATION_ID
                )
                .doesNotContain(
                        LegacyTedAuditFindingType.DOC_DOCUMENT_MISSING,
                        LegacyTedAuditFindingType.DOC_SOURCE_MISSING,
                        LegacyTedAuditFindingType.DOC_ORIGINAL_CONTENT_MISSING,
                        LegacyTedAuditFindingType.DOC_PRIMARY_REPRESENTATION_MISSING,
                        LegacyTedAuditFindingType.TED_PROJECTION_MISSING,
                        LegacyTedAuditFindingType.TED_PROJECTION_MISSING_LEGACY_LINK,
                        LegacyTedAuditFindingType.TED_PROJECTION_DOCUMENT_MISMATCH,
                        LegacyTedAuditFindingType.DOC_DEDUP_HASH_DUPLICATE
                );
    }

    private Page<ProcurementDocument> pageOf(ProcurementDocument... documents) {
        return new PageImpl<>(List.of(documents));
    }

    private String formatPackageIdentifier(int year, int serialNumber) {
        return "%04d%05d".formatted(year, serialNumber);
    }
}
