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

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

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.TargetFileCopyResult;
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileCopySuccess;
import de.gecheckt.pdf.umbenenner.application.port.out.TargetFileCopyTechnicalFailure;
import de.gecheckt.pdf.umbenenner.domain.model.SourceDocumentLocator;

/**
 * Tests for {@link FilesystemTargetFileCopyAdapter}.
 * <p>
 * Covers the happy path (copy via temp file and final move), source integrity,
 * technical failure cases, and cleanup after failure.
 */
class FilesystemTargetFileCopyAdapterTest {

    @TempDir
    Path sourceFolder;

    @TempDir
    Path targetFolder;

    private FilesystemTargetFileCopyAdapter adapter;

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

    // -------------------------------------------------------------------------
    // Happy path – successful copy
    // -------------------------------------------------------------------------

    @Test
    void copyToTarget_success_returnsTargetFileCopySuccess() throws IOException {
        Path sourceFile = createSourceFile("source.pdf", "PDF content");
        String resolvedFilename = "2026-01-15 - Rechnung.pdf";

        TargetFileCopyResult result = adapter.copyToTarget(
                new SourceDocumentLocator(sourceFile.toAbsolutePath().toString()),
                resolvedFilename);

        assertThat(result).isInstanceOf(TargetFileCopySuccess.class);
    }

    @Test
    void copyToTarget_success_targetFileCreatedWithCorrectContent() throws IOException {
        byte[] content = "PDF content bytes".getBytes();
        Path sourceFile = sourceFolder.resolve("invoice.pdf");
        Files.write(sourceFile, content);
        String resolvedFilename = "2026-01-15 - Rechnung.pdf";

        adapter.copyToTarget(
                new SourceDocumentLocator(sourceFile.toAbsolutePath().toString()),
                resolvedFilename);

        Path targetFile = targetFolder.resolve(resolvedFilename);
        assertThat(targetFile).exists();
        assertThat(Files.readAllBytes(targetFile)).isEqualTo(content);
    }

    @Test
    void copyToTarget_success_sourceFileRemainsUnchanged() throws IOException {
        byte[] originalContent = "original PDF content".getBytes();
        Path sourceFile = sourceFolder.resolve("source.pdf");
        Files.write(sourceFile, originalContent);
        String resolvedFilename = "2026-01-15 - Rechnung.pdf";

        adapter.copyToTarget(
                new SourceDocumentLocator(sourceFile.toAbsolutePath().toString()),
                resolvedFilename);

        // Source must remain completely unchanged
        assertThat(Files.readAllBytes(sourceFile)).isEqualTo(originalContent);
        assertThat(sourceFile).exists();
    }

    @Test
    void copyToTarget_success_noTempFileRemainsInTargetFolder() throws IOException {
        Path sourceFile = createSourceFile("source.pdf", "content");
        String resolvedFilename = "2026-04-07 - Bescheid.pdf";

        adapter.copyToTarget(
                new SourceDocumentLocator(sourceFile.toAbsolutePath().toString()),
                resolvedFilename);

        // The .tmp file must not remain after a successful copy
        Path tempFile = targetFolder.resolve(resolvedFilename + ".tmp");
        assertThat(tempFile).doesNotExist();
    }

    @Test
    void copyToTarget_success_finalFileNameIsResolved() throws IOException {
        Path sourceFile = createSourceFile("source.pdf", "data");
        String resolvedFilename = "2026-03-05 - Kontoauszug.pdf";

        adapter.copyToTarget(
                new SourceDocumentLocator(sourceFile.toAbsolutePath().toString()),
                resolvedFilename);

        assertThat(targetFolder.resolve(resolvedFilename)).exists();
    }

    // -------------------------------------------------------------------------
    // Technical failure – source file does not exist
    // -------------------------------------------------------------------------

    @Test
    void copyToTarget_sourceDoesNotExist_returnsTargetFileCopyTechnicalFailure() {
        String nonExistentSource = sourceFolder.resolve("nonexistent.pdf").toAbsolutePath().toString();

        TargetFileCopyResult result = adapter.copyToTarget(
                new SourceDocumentLocator(nonExistentSource),
                "2026-01-01 - Rechnung.pdf");

        assertThat(result).isInstanceOf(TargetFileCopyTechnicalFailure.class);
    }

    @Test
    void copyToTarget_sourceDoesNotExist_failureContainsSourcePath() {
        String nonExistentSource = sourceFolder.resolve("nonexistent.pdf").toAbsolutePath().toString();

        TargetFileCopyResult result = adapter.copyToTarget(
                new SourceDocumentLocator(nonExistentSource),
                "2026-01-01 - Rechnung.pdf");

        assertThat(result).isInstanceOf(TargetFileCopyTechnicalFailure.class);
        TargetFileCopyTechnicalFailure failure = (TargetFileCopyTechnicalFailure) result;
        assertThat(failure.errorMessage()).contains(nonExistentSource);
    }

    // -------------------------------------------------------------------------
    // Technical failure – target folder does not exist
    // -------------------------------------------------------------------------

    @Test
    void copyToTarget_targetFolderDoesNotExist_returnsTargetFileCopyTechnicalFailure()
            throws IOException {
        Path sourceFile = createSourceFile("source.pdf", "content");
        Path nonExistentTargetFolder = targetFolder.resolve("nonexistent-subfolder");
        FilesystemTargetFileCopyAdapter adapterWithMissingFolder =
                new FilesystemTargetFileCopyAdapter(nonExistentTargetFolder);

        TargetFileCopyResult result = adapterWithMissingFolder.copyToTarget(
                new SourceDocumentLocator(sourceFile.toAbsolutePath().toString()),
                "2026-01-01 - Rechnung.pdf");

        assertThat(result).isInstanceOf(TargetFileCopyTechnicalFailure.class);
    }

    // -------------------------------------------------------------------------
    // Cleanup after failure – no temp file left
    // -------------------------------------------------------------------------

    @Test
    void copyToTarget_sourceDoesNotExist_noTempFileLeftInTargetFolder() {
        String nonExistentSource = sourceFolder.resolve("missing.pdf").toAbsolutePath().toString();
        String resolvedFilename = "2026-01-01 - Test.pdf";

        adapter.copyToTarget(
                new SourceDocumentLocator(nonExistentSource),
                resolvedFilename);

        // Even though the copy failed, no temp file should remain
        Path tempFile = targetFolder.resolve(resolvedFilename + ".tmp");
        assertThat(tempFile).doesNotExist();
    }

    // -------------------------------------------------------------------------
    // TargetFileCopyTechnicalFailure semantics
    // -------------------------------------------------------------------------

    @Test
    void copyToTarget_failure_messageIsNonNull() {
        String nonExistentSource = sourceFolder.resolve("ghost.pdf").toAbsolutePath().toString();

        TargetFileCopyTechnicalFailure failure = (TargetFileCopyTechnicalFailure)
                adapter.copyToTarget(
                        new SourceDocumentLocator(nonExistentSource),
                        "2026-01-01 - Test.pdf");

        assertThat(failure.errorMessage()).isNotNull();
    }

    // -------------------------------------------------------------------------
    // Null guards
    // -------------------------------------------------------------------------

    @Test
    void copyToTarget_rejectsNullSourceLocator() throws IOException {
        assertThatNullPointerException()
                .isThrownBy(() -> adapter.copyToTarget(null, "2026-01-01 - Test.pdf"));
    }

    @Test
    void copyToTarget_rejectsNullResolvedFilename() throws IOException {
        Path sourceFile = createSourceFile("source.pdf", "content");
        assertThatNullPointerException()
                .isThrownBy(() -> adapter.copyToTarget(
                        new SourceDocumentLocator(sourceFile.toAbsolutePath().toString()),
                        null));
    }

    @Test
    void constructor_rejectsNullTargetFolderPath() {
        assertThatNullPointerException()
                .isThrownBy(() -> new FilesystemTargetFileCopyAdapter(null));
    }

    // -------------------------------------------------------------------------
    // Helpers
    // -------------------------------------------------------------------------

    private Path createSourceFile(String filename, String content) throws IOException {
        Path file = sourceFolder.resolve(filename);
        Files.writeString(file, content);
        return file;
    }
}
