package com.storycove.service;

import com.storycove.dto.EPUBImportRequest;
import com.storycove.dto.EPUBImportResponse;
import com.storycove.dto.ReadingPositionDto;
import com.storycove.entity.*;
import com.storycove.repository.ReadingPositionRepository;
import com.storycove.service.exception.InvalidFileException;
import com.storycove.service.exception.ResourceNotFoundException;

import nl.siegmann.epublib.domain.Book;
import nl.siegmann.epublib.domain.Metadata;
import nl.siegmann.epublib.domain.Resource;
import nl.siegmann.epublib.domain.SpineReference;
import nl.siegmann.epublib.epub.EpubReader;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

@Service
@Transactional
public class EPUBImportService {
    private static final Logger log = LoggerFactory.getLogger(EPUBImportService.class);
    
    private final StoryService storyService;
    private final AuthorService authorService;
    private final SeriesService seriesService;
    private final TagService tagService;
    private final ReadingPositionRepository readingPositionRepository;
    private final HtmlSanitizationService sanitizationService;
    private final ImageService imageService;
    private final LibraryService libraryService;

    @Autowired
    public EPUBImportService(StoryService storyService,
                           AuthorService authorService,
                           SeriesService seriesService,
                           TagService tagService,
                           ReadingPositionRepository readingPositionRepository,
                           HtmlSanitizationService sanitizationService,
                           ImageService imageService,
                           LibraryService libraryService) {
        this.storyService = storyService;
        this.authorService = authorService;
        this.seriesService = seriesService;
        this.tagService = tagService;
        this.readingPositionRepository = readingPositionRepository;
        this.sanitizationService = sanitizationService;
        this.imageService = imageService;
        this.libraryService = libraryService;
    }
    
    public EPUBImportResponse importEPUB(EPUBImportRequest request) {
        try {
            MultipartFile epubFile = request.getEpubFile();

            if (epubFile == null || epubFile.isEmpty()) {
                return EPUBImportResponse.error("EPUB file is required");
            }

            if (!isValidEPUBFile(epubFile)) {
                return EPUBImportResponse.error("Invalid EPUB file format");
            }

            log.info("Parsing EPUB file: {}", epubFile.getOriginalFilename());
            Book book = parseEPUBFile(epubFile);

            log.info("Creating story entity from EPUB metadata");
            Story story = createStoryFromEPUB(book, request);

            log.info("Saving story to database: {}", story.getTitle());
            Story savedStory = storyService.create(story);
            log.info("Story saved successfully with ID: {}", savedStory.getId());

            // Step 1: resolve images embedded in the EPUB archive (relative src paths)
            String currentContent = story.getContentHtml();
            if (currentContent != null && currentContent.contains("<img")) {
                try {
                    log.info("Resolving EPUB-embedded images for story: {}", savedStory.getId());
                    String resolvedContent = processEpubImages(currentContent, book, savedStory.getId());
                    if (!resolvedContent.equals(currentContent)) {
                        log.info("Updating story content with resolved EPUB images");
                        savedStory.setContentHtml(resolvedContent);
                        savedStory = storyService.update(savedStory.getId(), savedStory);
                        currentContent = resolvedContent;
                    }
                } catch (Exception e) {
                    log.error("EPUB Import - Failed to resolve embedded images for story {}: {}",
                            savedStory.getId(), e.getMessage(), e);
                }
            }

            // Step 2: download any remaining external (http/https) images
            if (currentContent != null && currentContent.contains("<img")) {
                try {
                    log.info("Processing external images for story: {}", savedStory.getId());
                    ImageService.ContentImageProcessingResult imageResult =
                        imageService.processContentImages(currentContent, savedStory.getId());

                    if (!imageResult.getProcessedContent().equals(currentContent)) {
                        log.info("Updating story content with downloaded external images");
                        savedStory.setContentHtml(imageResult.getProcessedContent());
                        savedStory = storyService.update(savedStory.getId(), savedStory);

                        log.info("EPUB Import - External image processing completed for story {}. Downloaded {} images.",
                                savedStory.getId(), imageResult.getDownloadedImages().size());

                        if (imageResult.hasWarnings()) {
                            log.warn("EPUB Import - Image processing warnings: {}",
                                    String.join(", ", imageResult.getWarnings()));
                        }
                    }
                } catch (Exception e) {
                    log.error("EPUB Import - Failed to process external images for story {}: {}",
                            savedStory.getId(), e.getMessage(), e);
                }
            }

            log.info("Building import response for story: {}", savedStory.getId());
            EPUBImportResponse response = EPUBImportResponse.success(savedStory.getId(), savedStory.getTitle());
            response.setWordCount(savedStory.getWordCount());
            response.setTotalChapters(book.getSpine().size());

            if (request.getPreserveReadingPosition() != null && request.getPreserveReadingPosition()) {
                log.info("Extracting and saving reading position");
                ReadingPosition readingPosition = extractReadingPosition(book, savedStory);
                if (readingPosition != null) {
                    ReadingPosition savedPosition = readingPositionRepository.save(readingPosition);
                    response.setReadingPosition(convertToDto(savedPosition));
                }
            }

            log.info("EPUB import completed successfully for: {}", savedStory.getTitle());
            return response;

        } catch (Exception e) {
            log.error("EPUB import failed with exception: {}", e.getMessage(), e);
            return EPUBImportResponse.error("Failed to import EPUB: " + e.getMessage());
        }
    }
    
    private boolean isValidEPUBFile(MultipartFile file) {
        String filename = file.getOriginalFilename();
        if (filename == null || !filename.toLowerCase().endsWith(".epub")) {
            return false;
        }
        
        String contentType = file.getContentType();
        return "application/epub+zip".equals(contentType) || 
               "application/zip".equals(contentType) ||
               contentType == null;
    }
    
    private Book parseEPUBFile(MultipartFile epubFile) throws IOException {
        try (InputStream inputStream = epubFile.getInputStream()) {
            EpubReader epubReader = new EpubReader();
            return epubReader.readEpub(inputStream);
        } catch (Exception e) {
            throw new InvalidFileException("Failed to parse EPUB file: " + e.getMessage());
        }
    }
    
    private Story createStoryFromEPUB(Book book, EPUBImportRequest request) {
        Metadata metadata = book.getMetadata();

        log.info("Extracting EPUB metadata");
        String title = extractTitle(metadata);
        String authorName = extractAuthorName(metadata, request);
        String description = extractDescription(metadata);

        log.info("Extracting and sanitizing content from {} chapters", book.getSpine().size());
        String content = extractContent(book);

        Story story = new Story();
        story.setTitle(title);
        story.setDescription(description);
        story.setContentHtml(sanitizationService.sanitize(content));

        // Extract and process cover image
        if (request.getExtractCover() == null || request.getExtractCover()) {
            log.info("Extracting cover image");
            String coverPath = extractAndSaveCoverImage(book);
            if (coverPath != null) {
                log.info("Cover image saved at: {}", coverPath);
                story.setCoverPath(coverPath);
            }
        }

        // Handle author assignment
        try {
            if (request.getAuthorId() != null) {
                log.info("Looking up author by ID: {}", request.getAuthorId());
                try {
                    Author author = authorService.findById(request.getAuthorId());
                    story.setAuthor(author);
                    log.info("Author found and assigned: {}", author.getName());
                } catch (ResourceNotFoundException e) {
                    log.warn("Author ID {} not found", request.getAuthorId());
                    if (request.getCreateMissingAuthor()) {
                        log.info("Creating new author: {}", authorName);
                        Author newAuthor = createAuthor(authorName);
                        story.setAuthor(newAuthor);
                        log.info("New author created with ID: {}", newAuthor.getId());
                    }
                }
            } else if (authorName != null && request.getCreateMissingAuthor()) {
                log.info("Finding or creating author: {}", authorName);
                Author author = findOrCreateAuthor(authorName);
                story.setAuthor(author);
                log.info("Author assigned: {} (ID: {})", author.getName(), author.getId());
            }
        } catch (Exception e) {
            log.error("Error handling author assignment: {}", e.getMessage(), e);
            throw e;
        }

        // Handle series assignment
        try {
            if (request.getSeriesId() != null && request.getSeriesVolume() != null) {
                log.info("Looking up series by ID: {}", request.getSeriesId());
                try {
                    Series series = seriesService.findById(request.getSeriesId());
                    story.setSeries(series);
                    story.setVolume(request.getSeriesVolume());
                    log.info("Series found and assigned: {} (volume {})", series.getName(), request.getSeriesVolume());
                } catch (ResourceNotFoundException e) {
                    log.warn("Series ID {} not found", request.getSeriesId());
                    if (request.getCreateMissingSeries() && request.getSeriesName() != null) {
                        log.info("Creating new series: {}", request.getSeriesName());
                        Series newSeries = createSeries(request.getSeriesName());
                        story.setSeries(newSeries);
                        story.setVolume(request.getSeriesVolume());
                        log.info("New series created with ID: {}", newSeries.getId());
                    }
                }
            }
        } catch (Exception e) {
            log.error("Error handling series assignment: {}", e.getMessage(), e);
            throw e;
        }

        // Handle tags from request or extract from EPUB metadata
        try {
            List<String> allTags = new ArrayList<>();
            if (request.getTags() != null && !request.getTags().isEmpty()) {
                allTags.addAll(request.getTags());
            }

            // Extract subjects/keywords from EPUB metadata
            List<String> epubTags = extractTags(metadata);
            if (epubTags != null && !epubTags.isEmpty()) {
                allTags.addAll(epubTags);
            }

            log.info("Processing {} tags for story", allTags.size());
            // Remove duplicates and create tags
            allTags.stream()
                    .distinct()
                    .forEach(tagName -> {
                        try {
                            log.debug("Finding or creating tag: {}", tagName);
                            Tag tag = tagService.findOrCreate(tagName.trim());
                            story.addTag(tag);
                        } catch (Exception e) {
                            log.error("Error creating tag '{}': {}", tagName, e.getMessage(), e);
                            throw e;
                        }
                    });
        } catch (Exception e) {
            log.error("Error handling tags: {}", e.getMessage(), e);
            throw e;
        }

        // Extract additional metadata for potential future use
        extractAdditionalMetadata(metadata, story);

        log.info("Story entity created successfully: {}", title);
        return story;
    }
    
    private String extractTitle(Metadata metadata) {
        List<String> titles = metadata.getTitles();
        if (titles != null && !titles.isEmpty()) {
            return titles.get(0);
        }
        return "Untitled EPUB";
    }
    
    private String extractAuthorName(Metadata metadata, EPUBImportRequest request) {
        if (request.getAuthorName() != null && !request.getAuthorName().trim().isEmpty()) {
            return request.getAuthorName().trim();
        }
        
        if (metadata.getAuthors() != null && !metadata.getAuthors().isEmpty()) {
            return metadata.getAuthors().get(0).getFirstname() + " " + metadata.getAuthors().get(0).getLastname();
        }
        
        return "Unknown Author";
    }
    
    private String extractDescription(Metadata metadata) {
        List<String> descriptions = metadata.getDescriptions();
        if (descriptions != null && !descriptions.isEmpty()) {
            String description = descriptions.get(0);
            // Truncate to 1000 characters if necessary
            if (description != null && description.length() > 1000) {
                log.info("Description exceeds 1000 characters ({}), truncating...", description.length());
                return description.substring(0, 997) + "...";
            }
            return description;
        }
        return null;
    }
    
    private List<String> extractTags(Metadata metadata) {
        List<String> tags = new ArrayList<>();
        
        // Extract subjects (main source of tags in EPUB)
        List<String> subjects = metadata.getSubjects();
        if (subjects != null && !subjects.isEmpty()) {
            tags.addAll(subjects);
        }
        
        // Extract keywords from meta tags
        String keywords = metadata.getMetaAttribute("keywords");
        if (keywords != null && !keywords.trim().isEmpty()) {
            String[] keywordArray = keywords.split("[,;]");
            for (String keyword : keywordArray) {
                String trimmed = keyword.trim();
                if (!trimmed.isEmpty()) {
                    tags.add(trimmed);
                }
            }
        }
        
        // Extract genre information
        String genre = metadata.getMetaAttribute("genre");
        if (genre != null && !genre.trim().isEmpty()) {
            tags.add(genre.trim());
        }
        
        return tags;
    }
    
    private void extractAdditionalMetadata(Metadata metadata, Story story) {
        // Extract language (could be useful for future i18n)
        String language = metadata.getLanguage();
        if (language != null && !language.trim().isEmpty()) {
            // Store as metadata in story description if needed
            // For now, we'll just log it for potential future use
            log.debug("EPUB Language: {}", language);
        }
        
        // Extract publisher information  
        List<String> publishers = metadata.getPublishers();
        if (publishers != null && !publishers.isEmpty()) {
            String publisher = publishers.get(0);
            // Could append to description or store separately in future
            log.debug("EPUB Publisher: {}", publisher);
        }
        
        // Extract publication date
        List<nl.siegmann.epublib.domain.Date> dates = metadata.getDates();
        if (dates != null && !dates.isEmpty()) {
            for (nl.siegmann.epublib.domain.Date date : dates) {
                log.debug("EPUB Date ({}): {}", date.getEvent(), date.getValue());
            }
        }
        
        // Extract ISBN or other identifiers
        List<nl.siegmann.epublib.domain.Identifier> identifiers = metadata.getIdentifiers();
        if (identifiers != null && !identifiers.isEmpty()) {
            for (nl.siegmann.epublib.domain.Identifier identifier : identifiers) {
                log.debug("EPUB Identifier ({}): {}", identifier.getScheme(), identifier.getValue());
            }
        }
    }
    
    private String extractContent(Book book) {
        StringBuilder contentBuilder = new StringBuilder();
        
        List<SpineReference> spine = book.getSpine().getSpineReferences();
        for (SpineReference spineRef : spine) {
            try {
                Resource resource = spineRef.getResource();
                if (resource != null && resource.getData() != null) {
                    String html = new String(resource.getData(), "UTF-8");
                    
                    Document doc = Jsoup.parse(html);
                    doc.select("script, style").remove();
                    
                    String chapterContent = doc.body() != null ? doc.body().html() : doc.html();
                    
                    contentBuilder.append("<div class=\"chapter\">")
                                 .append(chapterContent)
                                 .append("</div>");
                }
            } catch (Exception e) {
                // Skip this chapter on error
                continue;
            }
        }
        
        return contentBuilder.toString();
    }
    
    private Author findOrCreateAuthor(String authorName) {
        Optional<Author> existingAuthor = authorService.findByNameOptional(authorName);
        if (existingAuthor.isPresent()) {
            return existingAuthor.get();
        }
        return createAuthor(authorName);
    }
    
    private Author createAuthor(String authorName) {
        Author author = new Author();
        author.setName(authorName);
        return authorService.create(author);
    }
    
    private Series createSeries(String seriesName) {
        Series series = new Series();
        series.setName(seriesName);
        return seriesService.create(series);
    }
    
    private ReadingPosition extractReadingPosition(Book book, Story story) {
        try {
            Metadata metadata = book.getMetadata();
            
            String positionMeta = metadata.getMetaAttribute("reading-position");
            String cfiMeta = metadata.getMetaAttribute("epub-cfi");
            
            ReadingPosition position = new ReadingPosition(story);
            
            if (cfiMeta != null) {
                position.setEpubCfi(cfiMeta);
            }
            
            if (positionMeta != null) {
                try {
                    String[] parts = positionMeta.split(":");
                    if (parts.length >= 2) {
                        position.setChapterIndex(Integer.parseInt(parts[0]));
                        position.setWordPosition(Integer.parseInt(parts[1]));
                    }
                } catch (NumberFormatException e) {
                    // Ignore invalid position format
                }
            }
            
            return position;
            
        } catch (Exception e) {
            // Return null if no reading position found
            return null;
        }
    }
    
    /**
     * Resolves EPUB-internal image references (relative paths) by extracting the image
     * bytes from the EPUB resource map, saving them via ImageService, and replacing
     * the src attribute with the resulting local API URL.
     */
    private String processEpubImages(String htmlContent, Book book, java.util.UUID storyId) {
        if (htmlContent == null || !htmlContent.contains("<img")) {
            return htmlContent;
        }

        // Index all image resources by href and by bare filename for flexible lookup.
        // Also index resources whose media type epublib could not recognise (returns null for formats
        // like WebP that are absent from its built-in type registry) by falling back to the href extension.
        Map<String, Resource> byHref = new HashMap<>();
        Map<String, Resource> byFilename = new HashMap<>();
        for (Resource resource : book.getResources().getAll()) {
            String href = resource.getHref();
            if (href == null) continue;

            boolean isImageByMediaType = resource.getMediaType() != null &&
                    resource.getMediaType().toString().startsWith("image/");
            boolean isImageByExtension = hasImageExtension(href);

            if (isImageByMediaType || isImageByExtension) {
                byHref.put(href, resource);
                String filename = href.contains("/") ? href.substring(href.lastIndexOf('/') + 1) : href;
                byFilename.putIfAbsent(filename, resource);
            }
        }

        if (byHref.isEmpty()) {
            log.debug("No image resources found in EPUB for story: {}", storyId);
            return htmlContent;
        }

        String currentLibraryId = libraryService.getCurrentLibraryId();
        if (currentLibraryId == null || currentLibraryId.trim().isEmpty()) {
            currentLibraryId = "default";
        }

        org.jsoup.nodes.Document doc = Jsoup.parse(htmlContent);
        for (org.jsoup.nodes.Element img : doc.select("img[src]")) {
            String src = img.attr("src");

            // Skip already-resolved or external URLs
            if (src.startsWith("http://") || src.startsWith("https://") ||
                    src.startsWith("data:") || src.startsWith("/api/")) {
                continue;
            }

            Resource resource = resolveEpubResource(src, byHref, byFilename);
            if (resource == null) {
                log.warn("Could not find EPUB resource for image src: {}", src);
                continue;
            }

            try {
                byte[] imageData = resource.getData();
                if (imageData == null || imageData.length == 0) {
                    log.warn("EPUB image resource has no data for src: {}", src);
                    continue;
                }

                String mediaType = resource.getMediaType() != null ?
                        resource.getMediaType().toString() : getMediaTypeFromHref(resource.getHref());
                String extension = getExtensionFromMediaType(mediaType);
                String filename = "epub-img-" + System.currentTimeMillis() + "-" +
                        (int) (Math.random() * 100000) + "." + extension;

                MultipartFile imageFile = new EPUBCoverMultipartFile(imageData, filename, mediaType);
                String imagePath = imageService.uploadContentImage(imageFile, storyId);
                String imageUrl = "/api/files/images/" + currentLibraryId + "/" + imagePath;

                img.attr("src", imageUrl);
                log.debug("Resolved EPUB image: {} -> {}", src, imageUrl);

            } catch (Exception e) {
                log.error("Failed to save EPUB image {}: {}", src, e.getMessage(), e);
            }
        }

        return doc.body().html();
    }

    /**
     * Tries to match a relative EPUB src path against the resource maps.
     * Resolution order: exact href match → strip leading ../ segments → filename only.
     */
    private Resource resolveEpubResource(String src, Map<String, Resource> byHref, Map<String, Resource> byFilename) {
        if (byHref.containsKey(src)) {
            return byHref.get(src);
        }

        // Strip leading ../ and ./ navigation to get a plain relative path
        String normalized = src;
        while (normalized.startsWith("../")) {
            normalized = normalized.substring(3);
        }
        if (normalized.startsWith("./")) {
            normalized = normalized.substring(2);
        }

        if (byHref.containsKey(normalized)) {
            return byHref.get(normalized);
        }

        // Fall back to filename-only match
        String filename = normalized.contains("/") ?
                normalized.substring(normalized.lastIndexOf('/') + 1) : normalized;
        return byFilename.get(filename);
    }

    private String extractAndSaveCoverImage(Book book) {
        try {
            Resource coverResource = book.getCoverImage();
            if (coverResource != null && coverResource.getData() != null) {
                // Create a temporary MultipartFile from the EPUB cover data
                byte[] imageData = coverResource.getData();
                String mediaType = coverResource.getMediaType() != null ? 
                    coverResource.getMediaType().toString() : "image/jpeg";
                
                // Determine file extension from media type
                String extension = getExtensionFromMediaType(mediaType);
                String filename = "epub_cover_" + System.currentTimeMillis() + "." + extension;
                
                // Create a custom MultipartFile implementation for the cover image
                MultipartFile coverFile = new EPUBCoverMultipartFile(imageData, filename, mediaType);
                
                // Use ImageService to process and save the cover
                return imageService.uploadImage(coverFile, ImageService.ImageType.COVER);
            }
        } catch (Exception e) {
            // Log error but don't fail the import
            System.err.println("Failed to extract cover image: " + e.getMessage());
        }
        return null;
    }
    
    private String getExtensionFromMediaType(String mediaType) {
        switch (mediaType.toLowerCase()) {
            case "image/jpeg":
            case "image/jpg":
                return "jpg";
            case "image/png":
                return "png";
            case "image/gif":
                return "gif";
            case "image/webp":
                return "webp";
            default:
                return "jpg"; // Default fallback
        }
    }

    /**
     * Determine MIME type from the file extension in an EPUB resource href.
     * Used as a fallback when epublib does not recognise the media type (e.g. WebP).
     */
    private String getMediaTypeFromHref(String href) {
        if (href == null) return "image/jpeg";
        String lower = href.toLowerCase();
        if (lower.endsWith(".png"))  return "image/png";
        if (lower.endsWith(".gif"))  return "image/gif";
        if (lower.endsWith(".webp")) return "image/webp";
        if (lower.endsWith(".svg"))  return "image/svg+xml";
        return "image/jpeg"; // covers .jpg, .jpeg and unknown formats
    }

    /**
     * Returns true if the href path ends with a known image file extension.
     * Used to index EPUB resources whose media type epublib could not determine.
     */
    private boolean hasImageExtension(String href) {
        if (href == null) return false;
        String lower = href.toLowerCase();
        return lower.endsWith(".jpg")  || lower.endsWith(".jpeg") ||
               lower.endsWith(".png")  || lower.endsWith(".gif")  ||
               lower.endsWith(".webp") || lower.endsWith(".svg");
    }
    
    private ReadingPositionDto convertToDto(ReadingPosition position) {
        if (position == null) return null;
        
        ReadingPositionDto dto = new ReadingPositionDto();
        dto.setId(position.getId());
        dto.setStoryId(position.getStory().getId());
        dto.setChapterIndex(position.getChapterIndex());
        dto.setChapterTitle(position.getChapterTitle());
        dto.setWordPosition(position.getWordPosition());
        dto.setCharacterPosition(position.getCharacterPosition());
        dto.setPercentageComplete(position.getPercentageComplete());
        dto.setEpubCfi(position.getEpubCfi());
        dto.setContextBefore(position.getContextBefore());
        dto.setContextAfter(position.getContextAfter());
        dto.setCreatedAt(position.getCreatedAt());
        dto.setUpdatedAt(position.getUpdatedAt());
        
        return dto;
    }
    
    public List<String> validateEPUBFile(MultipartFile file) {
        List<String> errors = new ArrayList<>();
        
        if (file == null || file.isEmpty()) {
            errors.add("EPUB file is required");
            return errors;
        }
        
        if (!isValidEPUBFile(file)) {
            errors.add("Invalid EPUB file format. Only .epub files are supported");
        }
        
        if (file.getSize() > 100 * 1024 * 1024) { // 100MB limit
            errors.add("EPUB file size exceeds 100MB limit");
        }
        
        try {
            Book book = parseEPUBFile(file);
            if (book.getMetadata() == null) {
                errors.add("EPUB file contains no metadata");
            }
            if (book.getSpine() == null || book.getSpine().isEmpty()) {
                errors.add("EPUB file contains no readable content");
            }
        } catch (Exception e) {
            errors.add("Failed to parse EPUB file: " + e.getMessage());
        }
        
        return errors;
    }
    
    /**
     * Custom MultipartFile implementation for EPUB cover images
     */
    private static class EPUBCoverMultipartFile implements MultipartFile {
        private final byte[] data;
        private final String filename;
        private final String contentType;
        
        public EPUBCoverMultipartFile(byte[] data, String filename, String contentType) {
            this.data = data;
            this.filename = filename;
            this.contentType = contentType;
        }
        
        @Override
        public String getName() {
            return "coverImage";
        }
        
        @Override
        public String getOriginalFilename() {
            return filename;
        }
        
        @Override
        public String getContentType() {
            return contentType;
        }
        
        @Override
        public boolean isEmpty() {
            return data == null || data.length == 0;
        }
        
        @Override
        public long getSize() {
            return data != null ? data.length : 0;
        }
        
        @Override
        public byte[] getBytes() {
            return data;
        }
        
        @Override
        public InputStream getInputStream() {
            return new java.io.ByteArrayInputStream(data);
        }
        
        @Override
        public void transferTo(java.io.File dest) throws IOException {
            try (java.io.FileOutputStream fos = new java.io.FileOutputStream(dest)) {
                fos.write(data);
            }
        }
        
        @Override
        public void transferTo(java.nio.file.Path dest) throws IOException {
            java.nio.file.Files.write(dest, data);
        }
    }
}