package com.storycove.service;

import com.storycove.dto.EPUBExportRequest;
import com.storycove.entity.Collection;
import com.storycove.entity.ReadingPosition;
import com.storycove.entity.Story;
import com.storycove.repository.ReadingPositionRepository;
import com.storycove.service.exception.ResourceNotFoundException;

import nl.siegmann.epublib.domain.*;
import nl.siegmann.epublib.epub.EpubWriter;

import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Collectors;

@Service
@Transactional
public class EPUBExportService {
    
    private final StoryService storyService;
    private final ReadingPositionRepository readingPositionRepository;
    private final CollectionService collectionService;
    
    @Autowired
    public EPUBExportService(StoryService storyService,
                           ReadingPositionRepository readingPositionRepository,
                           CollectionService collectionService) {
        this.storyService = storyService;
        this.readingPositionRepository = readingPositionRepository;
        this.collectionService = collectionService;
    }
    
    public Resource exportStoryAsEPUB(EPUBExportRequest request) throws IOException {
        Story story = storyService.findById(request.getStoryId());
        
        Book book = createEPUBBook(story, request);
        
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        EpubWriter epubWriter = new EpubWriter();
        epubWriter.write(book, outputStream);
        
        return new ByteArrayResource(outputStream.toByteArray());
    }
    
    public Resource exportCollectionAsEPUB(UUID collectionId, EPUBExportRequest request) throws IOException {
        Collection collection = collectionService.findById(collectionId);
        List<Story> stories = collection.getCollectionStories().stream()
                .sorted((cs1, cs2) -> Integer.compare(cs1.getPosition(), cs2.getPosition()))
                .map(cs -> cs.getStory())
                .collect(Collectors.toList());
        
        if (stories.isEmpty()) {
            throw new ResourceNotFoundException("Collection contains no stories to export");
        }
        
        Book book = createCollectionEPUBBook(collection, stories, request);
        
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        EpubWriter epubWriter = new EpubWriter();
        epubWriter.write(book, outputStream);
        
        return new ByteArrayResource(outputStream.toByteArray());
    }
    
    private Book createEPUBBook(Story story, EPUBExportRequest request) throws IOException {
        Book book = new Book();
        
        setupMetadata(book, story, request);
        
        addCoverImage(book, story, request);
        
        addContent(book, story, request);
        
        addReadingPosition(book, story, request);
        
        return book;
    }
    
    private Book createCollectionEPUBBook(Collection collection, List<Story> stories, EPUBExportRequest request) throws IOException {
        Book book = new Book();
        
        setupCollectionMetadata(book, collection, stories, request);
        
        addCollectionCoverImage(book, collection, request);
        
        addCollectionContent(book, stories, request);
        
        return book;
    }
    
    private void setupMetadata(Book book, Story story, EPUBExportRequest request) {
        Metadata metadata = book.getMetadata();
        
        String title = request.getCustomTitle() != null ? 
                      request.getCustomTitle() : story.getTitle();
        metadata.addTitle(title);
        
        String authorName = request.getCustomAuthor() != null ? 
                          request.getCustomAuthor() : 
                          (story.getAuthor() != null ? story.getAuthor().getName() : "Unknown Author");
        metadata.addAuthor(new Author(authorName));
        
        metadata.setLanguage(request.getLanguage() != null ? request.getLanguage() : "en");
        
        metadata.addIdentifier(new Identifier("storycove", story.getId().toString()));
        
        if (story.getDescription() != null) {
            metadata.addDescription(story.getDescription());
        }
        
        if (request.getIncludeMetadata()) {
            metadata.addDate(new Date(java.util.Date.from(
                story.getCreatedAt().atZone(java.time.ZoneId.systemDefault()).toInstant()
            ), Date.Event.CREATION));
            
            if (story.getSeries() != null) {
                // Add series and metadata info to description instead of using addMeta
                StringBuilder description = new StringBuilder();
                if (story.getDescription() != null) {
                    description.append(story.getDescription()).append("\n\n");
                }
                
                description.append("Series: ").append(story.getSeries().getName());
                if (story.getVolume() != null) {
                    description.append(" (Volume ").append(story.getVolume()).append(")");
                }
                description.append("\n");
                
                if (story.getWordCount() != null) {
                    description.append("Word Count: ").append(story.getWordCount()).append("\n");
                }
                
                if (story.getRating() != null) {
                    description.append("Rating: ").append(story.getRating()).append("/5\n");
                }
                
                if (!story.getTags().isEmpty()) {
                    String tags = story.getTags().stream()
                        .map(tag -> tag.getName())
                        .reduce((a, b) -> a + ", " + b)
                        .orElse("");
                    description.append("Tags: ").append(tags).append("\n");
                }
                
                description.append("\nGenerated by StoryCove on ")
                          .append(LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
                
                metadata.addDescription(description.toString());
            }
        }
        
        if (request.getCustomMetadata() != null && !request.getCustomMetadata().isEmpty()) {
            // Add custom metadata to description since addMeta doesn't exist
            StringBuilder customDesc = new StringBuilder();
            for (String customMeta : request.getCustomMetadata()) {
                String[] parts = customMeta.split(":", 2);
                if (parts.length == 2) {
                    customDesc.append(parts[0].trim()).append(": ").append(parts[1].trim()).append("\n");
                }
            }
            if (customDesc.length() > 0) {
                String existingDesc = metadata.getDescriptions().isEmpty() ? "" : metadata.getDescriptions().get(0);
                metadata.addDescription(existingDesc + "\n" + customDesc.toString());
            }
        }
    }
    
    private void addCoverImage(Book book, Story story, EPUBExportRequest request) {
        if (!request.getIncludeCoverImage() || story.getCoverPath() == null) {
            return;
        }
        
        try {
            Path coverPath = Paths.get(story.getCoverPath());
            if (Files.exists(coverPath)) {
                byte[] coverImageData = Files.readAllBytes(coverPath);
                String mimeType = Files.probeContentType(coverPath);
                if (mimeType == null) {
                    mimeType = "image/jpeg";
                }
                
                nl.siegmann.epublib.domain.Resource coverResource = 
                    new nl.siegmann.epublib.domain.Resource(coverImageData, "cover.jpg");
                
                book.setCoverImage(coverResource);
            }
        } catch (IOException e) {
            // Skip cover image on error
        }
    }
    
    private void addContent(Book book, Story story, EPUBExportRequest request) {
        String content = story.getContentHtml();
        if (content == null) {
            content = story.getContentPlain() != null ? 
                     "<p>" + story.getContentPlain().replace("\n", "</p><p>") + "</p>" : 
                     "<p>No content available</p>";
        }
        
        if (request.getSplitByChapters()) {
            addChapterizedContent(book, content, request);
        } else {
            addSingleChapterContent(book, content, story);
        }
    }
    
    private void addSingleChapterContent(Book book, String content, Story story) {
        String html = createChapterHTML(story.getTitle(), content);
        
        nl.siegmann.epublib.domain.Resource chapterResource = 
            new nl.siegmann.epublib.domain.Resource(html.getBytes(), "chapter.html");
        
        book.addSection(story.getTitle(), chapterResource);
    }
    
    private void addChapterizedContent(Book book, String content, EPUBExportRequest request) {
        Document doc = Jsoup.parse(content);
        Elements chapters = doc.select("div.chapter, h1, h2, h3");
        
        if (chapters.isEmpty()) {
            List<String> paragraphs = splitByWords(content, 
                request.getMaxWordsPerChapter() != null ? request.getMaxWordsPerChapter() : 2000);
            
            for (int i = 0; i < paragraphs.size(); i++) {
                String chapterTitle = "Chapter " + (i + 1);
                String html = createChapterHTML(chapterTitle, paragraphs.get(i));
                
                nl.siegmann.epublib.domain.Resource chapterResource = 
                    new nl.siegmann.epublib.domain.Resource(html.getBytes(), "chapter" + (i + 1) + ".html");
                
                book.addSection(chapterTitle, chapterResource);
            }
        } else {
            for (int i = 0; i < chapters.size(); i++) {
                Element chapter = chapters.get(i);
                String chapterTitle = chapter.text();
                if (chapterTitle.trim().isEmpty()) {
                    chapterTitle = "Chapter " + (i + 1);
                }
                
                String chapterContent = chapter.html();
                String html = createChapterHTML(chapterTitle, chapterContent);
                
                nl.siegmann.epublib.domain.Resource chapterResource = 
                    new nl.siegmann.epublib.domain.Resource(html.getBytes(), "chapter" + (i + 1) + ".html");
                
                book.addSection(chapterTitle, chapterResource);
            }
        }
    }
    
    private List<String> splitByWords(String content, int maxWordsPerChapter) {
        String[] words = content.split("\\s+");
        List<String> chapters = new ArrayList<>();
        StringBuilder currentChapter = new StringBuilder();
        int wordCount = 0;
        
        for (String word : words) {
            currentChapter.append(word).append(" ");
            wordCount++;
            
            if (wordCount >= maxWordsPerChapter) {
                chapters.add(currentChapter.toString().trim());
                currentChapter = new StringBuilder();
                wordCount = 0;
            }
        }
        
        if (currentChapter.length() > 0) {
            chapters.add(currentChapter.toString().trim());
        }
        
        return chapters;
    }
    
    private String createChapterHTML(String title, String content) {
        return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
               "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" " +
               "\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\">" +
               "<html xmlns=\"http://www.w3.org/1999/xhtml\">" +
               "<head>" +
               "<title>" + escapeHtml(title) + "</title>" +
               "<style type=\"text/css\">" +
               "body { font-family: serif; margin: 1em; }" +
               "h1 { text-align: center; }" +
               "p { text-indent: 1em; margin: 0.5em 0; }" +
               "</style>" +
               "</head>" +
               "<body>" +
               "<h1>" + escapeHtml(title) + "</h1>" +
               fixHtmlForXhtml(content) +
               "</body>" +
               "</html>";
    }
    
    private void addReadingPosition(Book book, Story story, EPUBExportRequest request) {
        if (!request.getIncludeReadingPosition()) {
            return;
        }
        
        Optional<ReadingPosition> positionOpt = readingPositionRepository.findByStoryId(story.getId());
        if (positionOpt.isPresent()) {
            ReadingPosition position = positionOpt.get();
            Metadata metadata = book.getMetadata();
            
            // Add reading position to description since addMeta doesn't exist
            StringBuilder positionDesc = new StringBuilder();
            if (position.getEpubCfi() != null) {
                positionDesc.append("EPUB CFI: ").append(position.getEpubCfi()).append("\n");
            }
            
            if (position.getChapterIndex() != null && position.getWordPosition() != null) {
                positionDesc.append("Reading Position: Chapter ")
                           .append(position.getChapterIndex())
                           .append(", Word ").append(position.getWordPosition()).append("\n");
            }
            
            if (position.getPercentageComplete() != null) {
                positionDesc.append("Reading Progress: ")
                           .append(String.format("%.1f%%", position.getPercentageComplete())).append("\n");
            }
            
            positionDesc.append("Last Read: ")
                       .append(position.getUpdatedAt().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
            
            String existingDesc = metadata.getDescriptions().isEmpty() ? "" : metadata.getDescriptions().get(0);
            metadata.addDescription(existingDesc + "\n\n--- Reading Position ---\n" + positionDesc.toString());
        }
    }
    
    private String fixHtmlForXhtml(String html) {
        if (html == null) return "";
        
        // Fix common XHTML validation issues
        String fixed = html
            // Fix self-closing tags to be XHTML compliant
            .replaceAll("<br>", "<br />")
            .replaceAll("<hr>", "<hr />")
            .replaceAll("<img([^>]*)>", "<img$1 />")
            .replaceAll("<input([^>]*)>", "<input$1 />")
            .replaceAll("<area([^>]*)>", "<area$1 />")
            .replaceAll("<base([^>]*)>", "<base$1 />")
            .replaceAll("<col([^>]*)>", "<col$1 />")
            .replaceAll("<embed([^>]*)>", "<embed$1 />")
            .replaceAll("<link([^>]*)>", "<link$1 />")
            .replaceAll("<meta([^>]*)>", "<meta$1 />")
            .replaceAll("<param([^>]*)>", "<param$1 />")
            .replaceAll("<source([^>]*)>", "<source$1 />")
            .replaceAll("<track([^>]*)>", "<track$1 />")
            .replaceAll("<wbr([^>]*)>", "<wbr$1 />");
        
        return fixed;
    }
    
    private String escapeHtml(String text) {
        if (text == null) return "";
        return text.replace("&", "&amp;")
                  .replace("<", "&lt;")
                  .replace(">", "&gt;")
                  .replace("\"", "&quot;")
                  .replace("'", "&#39;");
    }
    
    public String getEPUBFilename(Story story) {
        StringBuilder filename = new StringBuilder();
        
        if (story.getAuthor() != null) {
            filename.append(sanitizeFilename(story.getAuthor().getName()))
                   .append(" - ");
        }
        
        filename.append(sanitizeFilename(story.getTitle()));
        
        if (story.getSeries() != null && story.getVolume() != null) {
            filename.append(" (")
                   .append(sanitizeFilename(story.getSeries().getName()))
                   .append(" ")
                   .append(story.getVolume())
                   .append(")");
        }
        
        filename.append(".epub");
        
        return filename.toString();
    }
    
    private String sanitizeFilename(String filename) {
        if (filename == null) return "unknown";
        return filename.replaceAll("[^a-zA-Z0-9._\\- ]", "")
                      .trim()
                      .replaceAll("\\s+", "_");
    }
    
    private void setupCollectionMetadata(Book book, Collection collection, List<Story> stories, EPUBExportRequest request) {
        Metadata metadata = book.getMetadata();
        
        String title = request.getCustomTitle() != null ? 
                      request.getCustomTitle() : collection.getName();
        metadata.addTitle(title);
        
        // Use collection creator as author, or combine story authors
        String authorName = "Collection";
        if (stories.size() == 1) {
            Story story = stories.get(0);
            authorName = story.getAuthor() != null ? story.getAuthor().getName() : "Unknown Author";
        } else {
            // For multiple stories, use "Various Authors" or collection name
            authorName = "Various Authors";
        }
        
        if (request.getCustomAuthor() != null) {
            authorName = request.getCustomAuthor();
        }
        
        metadata.addAuthor(new Author(authorName));
        metadata.setLanguage(request.getLanguage() != null ? request.getLanguage() : "en");
        metadata.addIdentifier(new Identifier("storycove-collection", collection.getId().toString()));
        
        // Create description from collection description and story list
        StringBuilder description = new StringBuilder();
        if (collection.getDescription() != null && !collection.getDescription().trim().isEmpty()) {
            description.append(collection.getDescription()).append("\n\n");
        }
        
        description.append("This collection contains ").append(stories.size()).append(" stories:\n");
        for (int i = 0; i < stories.size() && i < 10; i++) {
            Story story = stories.get(i);
            description.append((i + 1)).append(". ").append(story.getTitle());
            if (story.getAuthor() != null) {
                description.append(" by ").append(story.getAuthor().getName());
            }
            description.append("\n");
        }
        if (stories.size() > 10) {
            description.append("... and ").append(stories.size() - 10).append(" more stories.");
        }
        
        metadata.addDescription(description.toString());
        
        if (request.getIncludeMetadata()) {
            metadata.addDate(new Date(java.util.Date.from(
                collection.getCreatedAt().atZone(java.time.ZoneId.systemDefault()).toInstant()
            ), Date.Event.CREATION));
            
            // Add collection statistics to description
            int totalWordCount = stories.stream().mapToInt(s -> s.getWordCount() != null ? s.getWordCount() : 0).sum();
            description.append("\n\nTotal Word Count: ").append(totalWordCount);
            description.append("\nGenerated by StoryCove on ")
                      .append(LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
            
            metadata.addDescription(description.toString());
        }
    }
    
    private void addCollectionCoverImage(Book book, Collection collection, EPUBExportRequest request) {
        if (!request.getIncludeCoverImage()) {
            return;
        }
        
        try {
            // Try to use collection cover first
            if (collection.getCoverImagePath() != null) {
                Path coverPath = Paths.get(collection.getCoverImagePath());
                if (Files.exists(coverPath)) {
                    byte[] coverImageData = Files.readAllBytes(coverPath);
                    String mimeType = Files.probeContentType(coverPath);
                    if (mimeType == null) {
                        mimeType = "image/jpeg";
                    }
                    
                    nl.siegmann.epublib.domain.Resource coverResource = 
                        new nl.siegmann.epublib.domain.Resource(coverImageData, "collection-cover.jpg");
                    
                    book.setCoverImage(coverResource);
                    return;
                }
            }
            
            // TODO: Could generate a composite cover from story covers
            // For now, skip cover if collection doesn't have one
            
        } catch (IOException e) {
            // Skip cover image on error
        }
    }
    
    private void addCollectionContent(Book book, List<Story> stories, EPUBExportRequest request) {
        // Create table of contents chapter
        StringBuilder tocContent = new StringBuilder();
        tocContent.append("<h1>Table of Contents</h1>\n<ul>\n");
        
        for (int i = 0; i < stories.size(); i++) {
            Story story = stories.get(i);
            tocContent.append("<li><a href=\"#story").append(i + 1).append("\">")
                      .append(escapeHtml(story.getTitle()));
            if (story.getAuthor() != null) {
                tocContent.append(" by ").append(escapeHtml(story.getAuthor().getName()));
            }
            tocContent.append("</a></li>\n");
        }
        
        tocContent.append("</ul>\n");
        
        String tocHtml = createChapterHTML("Table of Contents", tocContent.toString());
        nl.siegmann.epublib.domain.Resource tocResource = 
            new nl.siegmann.epublib.domain.Resource(tocHtml.getBytes(), "toc.html");
        book.addSection("Table of Contents", tocResource);
        
        // Add each story as a chapter
        for (int i = 0; i < stories.size(); i++) {
            Story story = stories.get(i);
            String storyContent = story.getContentHtml();
            
            if (storyContent == null) {
                storyContent = story.getContentPlain() != null ? 
                             "<p>" + story.getContentPlain().replace("\n", "</p><p>") + "</p>" : 
                             "<p>No content available</p>";
            }
            
            // Add story metadata header
            StringBuilder storyHtml = new StringBuilder();
            storyHtml.append("<div id=\"story").append(i + 1).append("\">\n");
            storyHtml.append("<h1>").append(escapeHtml(story.getTitle())).append("</h1>\n");
            if (story.getAuthor() != null) {
                storyHtml.append("<p><em>by ").append(escapeHtml(story.getAuthor().getName())).append("</em></p>\n");
            }
            if (story.getDescription() != null && !story.getDescription().trim().isEmpty()) {
                storyHtml.append("<div class=\"summary\">\n")
                          .append("<p>").append(escapeHtml(story.getDescription())).append("</p>\n")
                          .append("</div>\n");
            }
            storyHtml.append("<hr />\n");
            storyHtml.append(fixHtmlForXhtml(storyContent));
            storyHtml.append("</div>\n");
            
            String chapterTitle = story.getTitle();
            if (story.getAuthor() != null) {
                chapterTitle += " by " + story.getAuthor().getName();
            }
            
            String html = createChapterHTML(chapterTitle, storyHtml.toString());
            nl.siegmann.epublib.domain.Resource storyResource = 
                new nl.siegmann.epublib.domain.Resource(html.getBytes(), "story" + (i + 1) + ".html");
            
            book.addSection(chapterTitle, storyResource);
        }
    }
    
    public boolean canExportStory(UUID storyId) {
        try {
            Story story = storyService.findById(storyId);
            return story.getContentHtml() != null || story.getContentPlain() != null;
        } catch (ResourceNotFoundException e) {
            return false;
        }
    }
    
    public String getCollectionEPUBFilename(Collection collection) {
        StringBuilder filename = new StringBuilder();
        filename.append(sanitizeFilename(collection.getName()));
        filename.append("_collection.epub");
        return filename.toString();
    }
}