package com.datamate.datamanagement.application;

import com.datamate.common.domain.service.FileService;
import com.datamate.datamanagement.domain.model.dataset.Dataset;
import com.datamate.datamanagement.domain.model.dataset.DatasetFile;
import com.datamate.datamanagement.infrastructure.persistence.repository.DatasetFileRepository;
import com.datamate.datamanagement.infrastructure.persistence.repository.DatasetRepository;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.util.List;

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

@ExtendWith(MockitoExtension.class)
class DatasetFileApplicationServiceVersioningTest {

    @TempDir
    Path tempDir;

    @Mock
    DatasetFileRepository datasetFileRepository;

    @Mock
    DatasetRepository datasetRepository;

    @Mock
    FileService fileService;

    @Mock
    PdfTextExtractAsyncService pdfTextExtractAsyncService;

    @Mock
    DatasetFilePreviewService datasetFilePreviewService;

    @Test
    void copyFilesToDatasetDirWithSourceRoot_shouldArchiveOldFileAndCreateNewVersionWhenDuplicateLogicalPath()
        throws Exception {
        String datasetId = "dataset-1";

        Path datasetRoot = tempDir.resolve("dataset-root");
        Files.createDirectories(datasetRoot);

        Path sourceRoot = tempDir.resolve("source-root");
        Files.createDirectories(sourceRoot);

        Path existingPath = datasetRoot.resolve("a.txt");
        Files.writeString(existingPath, "old-content", StandardCharsets.UTF_8);

        Path incomingPath = sourceRoot.resolve("a.txt");
        Files.writeString(incomingPath, "new-content", StandardCharsets.UTF_8);

        Dataset dataset = new Dataset();
        dataset.setId(datasetId);
        dataset.setPath(datasetRoot.toString());

        DatasetFile oldRecord = DatasetFile.builder()
            .id("old-file-id")
            .datasetId(datasetId)
            .fileName("a.txt")
            .filePath(existingPath.toString())
            .logicalPath(null)
            .version(null)
            .status(null)
            .fileSize(Files.size(existingPath))
            .build();

        when(datasetRepository.getById(datasetId)).thenReturn(dataset);
        when(datasetFileRepository.findAllVisibleByDatasetId(datasetId)).thenReturn(List.of(oldRecord));
        when(datasetFileRepository.findLatestByDatasetIdAndLogicalPath(anyString(), anyString())).thenReturn(null);

        DatasetFileApplicationService service = new DatasetFileApplicationService(
            datasetFileRepository,
            datasetRepository,
            fileService,
            pdfTextExtractAsyncService,
            datasetFilePreviewService
        );

        List<DatasetFile> copied = service.copyFilesToDatasetDirWithSourceRoot(
            datasetId,
            sourceRoot,
            List.of(incomingPath.toString())
        );

        assertThat(copied).hasSize(1);
        assertThat(Files.readString(existingPath, StandardCharsets.UTF_8)).isEqualTo("new-content");

        String logicalPathHash = sha256Hex("a.txt");
        Path archivedPath = datasetRoot
            .resolve(".datamate")
            .resolve("versions")
            .resolve(logicalPathHash)
            .resolve("v1")
            .resolve("old-file-id__a.txt")
            .toAbsolutePath()
            .normalize();

        assertThat(Files.exists(archivedPath)).isTrue();
        assertThat(Files.readString(archivedPath, StandardCharsets.UTF_8)).isEqualTo("old-content");

        ArgumentCaptor<DatasetFile> archivedCaptor = ArgumentCaptor.forClass(DatasetFile.class);
        verify(datasetFileRepository).updateById(archivedCaptor.capture());
        DatasetFile archivedRecord = archivedCaptor.getValue();
        assertThat(archivedRecord.getId()).isEqualTo("old-file-id");
        assertThat(archivedRecord.getStatus()).isEqualTo("ARCHIVED");
        assertThat(archivedRecord.getLogicalPath()).isEqualTo("a.txt");
        assertThat(archivedRecord.getVersion()).isEqualTo(1L);
        assertThat(Paths.get(archivedRecord.getFilePath()).toAbsolutePath().normalize()).isEqualTo(archivedPath);

        ArgumentCaptor<DatasetFile> createdCaptor = ArgumentCaptor.forClass(DatasetFile.class);
        verify(datasetFileRepository).saveOrUpdate(createdCaptor.capture());
        DatasetFile newRecord = createdCaptor.getValue();
        assertThat(newRecord.getId()).isNotEqualTo("old-file-id");
        assertThat(newRecord.getStatus()).isEqualTo("ACTIVE");
        assertThat(newRecord.getLogicalPath()).isEqualTo("a.txt");
        assertThat(newRecord.getVersion()).isEqualTo(2L);
        assertThat(Paths.get(newRecord.getFilePath()).toAbsolutePath().normalize()).isEqualTo(existingPath.toAbsolutePath().normalize());
    }

    private static String sha256Hex(String value) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hashed = digest.digest((value == null ? "" : value).getBytes(StandardCharsets.UTF_8));
            StringBuilder builder = new StringBuilder(hashed.length * 2);
            for (byte b : hashed) {
                builder.append(String.format("%02x", b));
            }
            return builder.toString();
        } catch (Exception e) {
            return Integer.toHexString((value == null ? "" : value).hashCode());
        }
    }
}

