package de.gecheckt.pdf.umbenenner.adapter.out.fingerprint;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;

import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintResult;
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintSuccess;
import de.gecheckt.pdf.umbenenner.application.port.out.FingerprintTechnicalError;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentCandidate;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;

/**
 * Unit tests for {@link Sha256FingerprintAdapter}.
 */
class Sha256FingerprintAdapterTest {

    private Sha256FingerprintAdapter adapter;

    @TempDir
    Path tempDir;

    @BeforeEach
    void setUp() {
        adapter = new Sha256FingerprintAdapter();
    }

    @Test
    void computeFingerprint_shouldReturnSuccess_whenFileExistsAndReadable() throws IOException {
        // Given
        String content = "Test PDF content for fingerprinting";
        Path testFile = tempDir.resolve("test.pdf");
        Files.write(testFile, content.getBytes());
        
        SourceDocumentLocator locator = new SourceDocumentLocator(testFile.toString());
        SourceDocumentCandidate candidate = new SourceDocumentCandidate("test.pdf", content.length(), locator);

        // When
        FingerprintResult result = adapter.computeFingerprint(candidate);

        // Then
        assertThat(result).isInstanceOf(FingerprintSuccess.class);
        FingerprintSuccess success = (FingerprintSuccess) result;
        assertThat(success.fingerprint().sha256Hex()).hasSize(64);
        assertThat(success.fingerprint().sha256Hex()).matches("[0-9a-f]{64}");
    }

    @Test
    void computeFingerprint_shouldReturnSameFingerprint_forSameContent() throws IOException {
        // Given
        String content = "Identical content for testing deterministic behavior";
        Path testFile1 = tempDir.resolve("test1.pdf");
        Path testFile2 = tempDir.resolve("test2.pdf");
        Files.write(testFile1, content.getBytes());
        Files.write(testFile2, content.getBytes());
        
        SourceDocumentLocator locator1 = new SourceDocumentLocator(testFile1.toString());
        SourceDocumentLocator locator2 = new SourceDocumentLocator(testFile2.toString());
        SourceDocumentCandidate candidate1 = new SourceDocumentCandidate("test1.pdf", content.length(), locator1);
        SourceDocumentCandidate candidate2 = new SourceDocumentCandidate("test2.pdf", content.length(), locator2);

        // When
        FingerprintResult result1 = adapter.computeFingerprint(candidate1);
        FingerprintResult result2 = adapter.computeFingerprint(candidate2);

        // Then
        assertThat(result1).isInstanceOf(FingerprintSuccess.class);
        assertThat(result2).isInstanceOf(FingerprintSuccess.class);
        
        FingerprintSuccess success1 = (FingerprintSuccess) result1;
        FingerprintSuccess success2 = (FingerprintSuccess) result2;
        
        assertThat(success1.fingerprint().sha256Hex())
            .isEqualTo(success2.fingerprint().sha256Hex());
    }

    @Test
    void computeFingerprint_shouldReturnDifferentFingerprints_forDifferentContent() throws IOException {
        // Given
        String content1 = "First PDF content";
        String content2 = "Second PDF content";
        Path testFile1 = tempDir.resolve("test1.pdf");
        Path testFile2 = tempDir.resolve("test2.pdf");
        Files.write(testFile1, content1.getBytes());
        Files.write(testFile2, content2.getBytes());
        
        SourceDocumentLocator locator1 = new SourceDocumentLocator(testFile1.toString());
        SourceDocumentLocator locator2 = new SourceDocumentLocator(testFile2.toString());
        SourceDocumentCandidate candidate1 = new SourceDocumentCandidate("test1.pdf", content1.length(), locator1);
        SourceDocumentCandidate candidate2 = new SourceDocumentCandidate("test2.pdf", content2.length(), locator2);

        // When
        FingerprintResult result1 = adapter.computeFingerprint(candidate1);
        FingerprintResult result2 = adapter.computeFingerprint(candidate2);

        // Then
        assertThat(result1).isInstanceOf(FingerprintSuccess.class);
        assertThat(result2).isInstanceOf(FingerprintSuccess.class);
        
        FingerprintSuccess success1 = (FingerprintSuccess) result1;
        FingerprintSuccess success2 = (FingerprintSuccess) result2;
        
        assertThat(success1.fingerprint().sha256Hex())
            .isNotEqualTo(success2.fingerprint().sha256Hex());
    }

    @Test
    void computeFingerprint_shouldReturnTechnicalError_whenFileDoesNotExist() {
        // Given
        Path nonExistentFile = tempDir.resolve("nonexistent.pdf");
        SourceDocumentLocator locator = new SourceDocumentLocator(nonExistentFile.toString());
        SourceDocumentCandidate candidate = new SourceDocumentCandidate("nonexistent.pdf", 0, locator);

        // When
        FingerprintResult result = adapter.computeFingerprint(candidate);

        // Then
        assertThat(result).isInstanceOf(FingerprintTechnicalError.class);
        FingerprintTechnicalError error = (FingerprintTechnicalError) result;
        assertThat(error.errorMessage()).contains("nonexistent.pdf");
        assertThat(error.errorMessage()).contains("Failed to read file");
        assertThat(error.cause()).isNotNull();
    }

    @Test
    void computeFingerprint_shouldReturnTechnicalError_whenLocatorValueIsInvalid() {
        // Given
        SourceDocumentLocator locator = new SourceDocumentLocator("\0invalid\0path");
        SourceDocumentCandidate candidate = new SourceDocumentCandidate("invalid.pdf", 0, locator);

        // When
        FingerprintResult result = adapter.computeFingerprint(candidate);

        // Then
        assertThat(result).isInstanceOf(FingerprintTechnicalError.class);
        FingerprintTechnicalError error = (FingerprintTechnicalError) result;
        assertThat(error.errorMessage()).contains("invalid.pdf");
        assertThat(error.errorMessage()).contains("Invalid file path");
        assertThat(error.cause()).isNotNull();
    }

    @Test
    void computeFingerprint_shouldThrowNullPointerException_whenCandidateIsNull() {
        // When & Then
        assertThatThrownBy(() -> adapter.computeFingerprint(null))
            .isInstanceOf(NullPointerException.class)
            .hasMessage("candidate must not be null");
    }
}