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

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingFailure;
import de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingResult;
import de.gecheckt.pdf.umbenenner.application.port.out.PromptLoadingSuccess;
import de.gecheckt.pdf.umbenenner.application.port.out.PromptPort;
import de.gecheckt.pdf.umbenenner.domain.model.PromptIdentifier;

/**
 * Filesystem-based implementation of {@link PromptPort}.
 * <p>
 * Loads prompt templates from an external file on disk and derives a stable identifier
 * from the filename. Ensures that empty or technically unusable prompts are rejected.
 * <p>
 * <strong>Identifier derivation:</strong>
 * The stable prompt identifier is derived from the filename of the prompt file.
 * This ensures deterministic, reproducible identification across batch runs.
 * For example, a prompt file named {@code "prompt_de_v2.txt"} receives the identifier
 * {@code "prompt_de_v2.txt"}.
 * <p>
 * <strong>Content validation:</strong>
 * After loading, the prompt content is trimmed and validated to ensure it is not empty.
 * An empty prompt (or one containing only whitespace) is considered technically unusable
 * and results in a {@link PromptLoadingFailure}.
 * <p>
 * <strong>Error handling:</strong>
 * All technical failures (file not found, I/O errors, permission issues) are caught
 * and returned as {@link PromptLoadingFailure} rather than thrown as exceptions.
 */
public class FilesystemPromptPortAdapter implements PromptPort {

    private static final Logger LOG = LogManager.getLogger(FilesystemPromptPortAdapter.class);

    private final Path promptFilePath;

    /**
     * Creates the adapter with the configured prompt file path.
     *
     * @param promptFilePath the path to the prompt template file; must not be null
     * @throws NullPointerException if promptFilePath is null
     */
    public FilesystemPromptPortAdapter(Path promptFilePath) {
        this.promptFilePath = Objects.requireNonNull(promptFilePath, "promptFilePath must not be null");
    }

    @Override
    public PromptLoadingResult loadPrompt() {
        try {
            if (!Files.exists(promptFilePath)) {
                return new PromptLoadingFailure(
                        "FILE_NOT_FOUND",
                        "Prompt file not found at: " + promptFilePath);
            }

            String content = Files.readString(promptFilePath, StandardCharsets.UTF_8);
            String trimmedContent = content.trim();

            if (trimmedContent.isEmpty()) {
                return new PromptLoadingFailure(
                        "EMPTY_CONTENT",
                        "Prompt file is empty or contains only whitespace: " + promptFilePath);
            }

            PromptIdentifier identifier = deriveIdentifier();
            LOG.debug("Prompt loaded successfully from {}", promptFilePath);
            return new PromptLoadingSuccess(identifier, trimmedContent);

        } catch (IOException e) {
            LOG.error("Failed to load prompt file: {}", promptFilePath, e);
            return new PromptLoadingFailure(
                    "IO_ERROR",
                    "Failed to read prompt file: " + e.getMessage());
        }
    }

    /**
     * Derives a stable prompt identifier from the filename.
     * <p>
     * The identifier is simply the filename (without the directory path).
     * This ensures that the same prompt file always receives the same identifier.
     *
     * @return a stable PromptIdentifier based on the filename
     */
    private PromptIdentifier deriveIdentifier() {
        String filename = promptFilePath.getFileName().toString();
        return new PromptIdentifier(filename);
    }
}
