package com.storycove.service;

import com.storycove.dto.*;
import com.storycove.service.exception.InvalidFileException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

@Service
public class ZIPImportService {
    private static final Logger log = LoggerFactory.getLogger(ZIPImportService.class);

    private static final long MAX_ZIP_SIZE = 1024L * 1024 * 1024; // 1GB
    private static final int MAX_FILES_IN_ZIP = 100;
    private static final long ZIP_SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes

    // Temporary storage for extracted ZIP files (sessionId -> session data)
    private final Map<String, ZIPSession> activeSessions = new ConcurrentHashMap<>();

    private final EPUBImportService epubImportService;
    private final PDFImportService pdfImportService;

    @Autowired
    public ZIPImportService(EPUBImportService epubImportService,
                           PDFImportService pdfImportService) {
        this.epubImportService = epubImportService;
        this.pdfImportService = pdfImportService;
    }

    /**
     * Analyze a ZIP file and return information about its contents
     */
    public ZIPAnalysisResponse analyzeZIPFile(MultipartFile zipFile) {
        try {
            // Validate ZIP file
            if (zipFile == null || zipFile.isEmpty()) {
                return ZIPAnalysisResponse.error("ZIP file is required");
            }

            if (!isValidZIPFile(zipFile)) {
                return ZIPAnalysisResponse.error("Invalid ZIP file format");
            }

            if (zipFile.getSize() > MAX_ZIP_SIZE) {
                return ZIPAnalysisResponse.error("ZIP file size exceeds " + (MAX_ZIP_SIZE / 1024 / 1024) + "MB limit");
            }

            log.info("Analyzing ZIP file: {} (size: {} bytes)", zipFile.getOriginalFilename(), zipFile.getSize());

            // Create temporary directory for extraction
            String sessionId = UUID.randomUUID().toString();
            Path tempDir = Files.createTempDirectory("storycove-zip-" + sessionId);

            // Extract ZIP contents
            List<FileInfoDto> files = extractAndAnalyzeZIP(zipFile, tempDir, sessionId);

            if (files.isEmpty()) {
                cleanupSession(sessionId);
                return ZIPAnalysisResponse.error("No valid EPUB or PDF files found in ZIP");
            }

            if (files.size() > MAX_FILES_IN_ZIP) {
                cleanupSession(sessionId);
                return ZIPAnalysisResponse.error("ZIP contains too many files (max " + MAX_FILES_IN_ZIP + ")");
            }

            // Store session data
            ZIPSession session = new ZIPSession(sessionId, tempDir, files);
            activeSessions.put(sessionId, session);

            // Schedule cleanup
            scheduleSessionCleanup(sessionId);

            ZIPAnalysisResponse response = ZIPAnalysisResponse.success(zipFile.getOriginalFilename(), files);
            response.addWarning("Session ID: " + sessionId + " (valid for 30 minutes)");

            log.info("ZIP analysis completed. Session ID: {}, Files found: {}", sessionId, files.size());
            return response;

        } catch (Exception e) {
            log.error("Failed to analyze ZIP file: {}", e.getMessage(), e);
            return ZIPAnalysisResponse.error("Failed to analyze ZIP file: " + e.getMessage());
        }
    }

    /**
     * Import selected files from a previously analyzed ZIP
     */
    public ZIPImportResponse importFromZIP(ZIPImportRequest request) {
        try {
            // Validate session
            ZIPSession session = activeSessions.get(request.getZipSessionId());
            if (session == null) {
                return createErrorResponse("Invalid or expired session ID");
            }

            if (session.isExpired()) {
                cleanupSession(request.getZipSessionId());
                return createErrorResponse("Session has expired. Please re-upload the ZIP file");
            }

            List<String> selectedFiles = request.getSelectedFiles();
            if (selectedFiles == null || selectedFiles.isEmpty()) {
                return createErrorResponse("No files selected for import");
            }

            log.info("Importing {} files from ZIP session: {}", selectedFiles.size(), request.getZipSessionId());

            List<FileImportResponse> results = new ArrayList<>();

            // Import each selected file
            for (String fileName : selectedFiles) {
                try {
                    FileInfoDto fileInfo = session.getFileInfo(fileName);
                    if (fileInfo == null) {
                        FileImportResponse errorResult = FileImportResponse.error("File not found in session: " + fileName, fileName);
                        results.add(errorResult);
                        continue;
                    }

                    if (fileInfo.getError() != null) {
                        FileImportResponse errorResult = FileImportResponse.error("File has errors: " + fileInfo.getError(), fileName);
                        results.add(errorResult);
                        continue;
                    }

                    // Get file-specific or default metadata
                    ZIPImportRequest.FileImportMetadata metadata = getFileMetadata(request, fileName);

                    // Import based on file type
                    FileImportResponse result;
                    if ("EPUB".equals(fileInfo.getFileType())) {
                        result = importEPUBFromSession(session, fileName, metadata, request);
                    } else if ("PDF".equals(fileInfo.getFileType())) {
                        result = importPDFFromSession(session, fileName, metadata, request);
                    } else {
                        result = FileImportResponse.error("Unsupported file type: " + fileInfo.getFileType(), fileName);
                    }

                    results.add(result);

                    if (result.isSuccess()) {
                        log.info("Successfully imported file: {} (Story ID: {})", fileName, result.getStoryId());
                    } else {
                        log.warn("Failed to import file: {} - {}", fileName, result.getMessage());
                    }

                } catch (Exception e) {
                    log.error("Failed to import file {}: {}", fileName, e.getMessage(), e);
                    FileImportResponse errorResult = FileImportResponse.error("Import failed: " + e.getMessage(), fileName);
                    results.add(errorResult);
                }
            }

            // Cleanup session after import
            cleanupSession(request.getZipSessionId());

            log.info("ZIP import completed. Total: {}, Success: {}, Failed: {}",
                    results.size(),
                    results.stream().filter(FileImportResponse::isSuccess).count(),
                    results.stream().filter(r -> !r.isSuccess()).count());

            return ZIPImportResponse.create(results);

        } catch (Exception e) {
            log.error("ZIP import failed: {}", e.getMessage(), e);
            return createErrorResponse("Import failed: " + e.getMessage());
        }
    }

    private boolean isValidZIPFile(MultipartFile file) {
        String filename = file.getOriginalFilename();
        if (filename == null || !filename.toLowerCase().endsWith(".zip")) {
            return false;
        }

        String contentType = file.getContentType();
        return "application/zip".equals(contentType) ||
               "application/x-zip-compressed".equals(contentType) ||
               contentType == null;
    }

    private List<FileInfoDto> extractAndAnalyzeZIP(MultipartFile zipFile, Path tempDir, String sessionId) throws IOException {
        List<FileInfoDto> files = new ArrayList<>();
        int fileCount = 0;

        try (ZipInputStream zis = new ZipInputStream(zipFile.getInputStream())) {
            ZipEntry entry;

            while ((entry = zis.getNextEntry()) != null) {
                // Skip directories
                if (entry.isDirectory()) {
                    continue;
                }

                // Only process root-level files
                String entryName = entry.getName();
                if (entryName.contains("/") || entryName.contains("\\")) {
                    log.debug("Skipping nested file: {}", entryName);
                    continue;
                }

                // Check if it's an EPUB or PDF
                String lowerName = entryName.toLowerCase();
                if (!lowerName.endsWith(".epub") && !lowerName.endsWith(".pdf")) {
                    log.debug("Skipping non-EPUB/PDF file: {}", entryName);
                    continue;
                }

                fileCount++;
                if (fileCount > MAX_FILES_IN_ZIP) {
                    log.warn("ZIP contains more than {} files, stopping extraction", MAX_FILES_IN_ZIP);
                    break;
                }

                // Extract file to temp directory
                Path extractedFile = tempDir.resolve(entryName);
                Files.copy(zis, extractedFile);

                // Analyze the extracted file
                FileInfoDto fileInfo = analyzeExtractedFile(extractedFile, entryName);
                files.add(fileInfo);

                zis.closeEntry();
            }
        }

        return files;
    }

    private FileInfoDto analyzeExtractedFile(Path filePath, String fileName) {
        try {
            long fileSize = Files.size(filePath);
            String fileType;
            String extractedTitle = null;
            String extractedAuthor = null;
            boolean hasMetadata = false;

            if (fileName.toLowerCase().endsWith(".epub")) {
                fileType = "EPUB";
                // Try to extract EPUB metadata
                try {
                    // Create a temporary MultipartFile for validation
                    byte[] fileBytes = Files.readAllBytes(filePath);
                    MultipartFile tempFile = new TempMultipartFile(fileBytes, fileName, "application/epub+zip");

                    // Use EPUBImportService to extract metadata
                    // For now, we'll just validate the file
                    List<String> errors = epubImportService.validateEPUBFile(tempFile);
                    if (!errors.isEmpty()) {
                        FileInfoDto errorInfo = new FileInfoDto(fileName, fileType, fileSize);
                        errorInfo.setError(String.join(", ", errors));
                        return errorInfo;
                    }

                    hasMetadata = true;
                    // We could extract more metadata here if needed
                } catch (Exception e) {
                    log.warn("Failed to extract EPUB metadata for {}: {}", fileName, e.getMessage());
                }
            } else if (fileName.toLowerCase().endsWith(".pdf")) {
                fileType = "PDF";
                // Try to extract PDF metadata
                try {
                    byte[] fileBytes = Files.readAllBytes(filePath);
                    MultipartFile tempFile = new TempMultipartFile(fileBytes, fileName, "application/pdf");

                    // Use PDFImportService to validate
                    List<String> errors = pdfImportService.validatePDFFile(tempFile);
                    if (!errors.isEmpty()) {
                        FileInfoDto errorInfo = new FileInfoDto(fileName, fileType, fileSize);
                        errorInfo.setError(String.join(", ", errors));
                        return errorInfo;
                    }

                    hasMetadata = true;
                    // We could extract more metadata here if needed
                } catch (Exception e) {
                    log.warn("Failed to extract PDF metadata for {}: {}", fileName, e.getMessage());
                }
            } else {
                FileInfoDto errorInfo = new FileInfoDto(fileName, "UNKNOWN", fileSize);
                errorInfo.setError("Unsupported file type");
                return errorInfo;
            }

            FileInfoDto fileInfo = new FileInfoDto(fileName, fileType, fileSize);
            fileInfo.setExtractedTitle(extractedTitle);
            fileInfo.setExtractedAuthor(extractedAuthor);
            fileInfo.setHasMetadata(hasMetadata);

            return fileInfo;

        } catch (Exception e) {
            log.error("Failed to analyze file {}: {}", fileName, e.getMessage(), e);
            FileInfoDto errorInfo = new FileInfoDto(fileName, "UNKNOWN", 0L);
            errorInfo.setError("Failed to analyze file: " + e.getMessage());
            return errorInfo;
        }
    }

    private ZIPImportRequest.FileImportMetadata getFileMetadata(ZIPImportRequest request, String fileName) {
        // Check for file-specific metadata first
        if (request.getFileMetadata() != null && request.getFileMetadata().containsKey(fileName)) {
            return request.getFileMetadata().get(fileName);
        }

        // Return default metadata
        ZIPImportRequest.FileImportMetadata metadata = new ZIPImportRequest.FileImportMetadata();
        metadata.setAuthorId(request.getDefaultAuthorId());
        metadata.setAuthorName(request.getDefaultAuthorName());
        metadata.setSeriesId(request.getDefaultSeriesId());
        metadata.setSeriesName(request.getDefaultSeriesName());
        metadata.setTags(request.getDefaultTags());

        return metadata;
    }

    private FileImportResponse importEPUBFromSession(ZIPSession session, String fileName,
                                                     ZIPImportRequest.FileImportMetadata metadata,
                                                     ZIPImportRequest request) throws IOException {
        Path filePath = session.getTempDir().resolve(fileName);
        byte[] fileBytes = Files.readAllBytes(filePath);

        MultipartFile epubFile = new TempMultipartFile(fileBytes, fileName, "application/epub+zip");

        EPUBImportRequest epubRequest = new EPUBImportRequest();
        epubRequest.setEpubFile(epubFile);
        epubRequest.setAuthorId(metadata.getAuthorId());
        epubRequest.setAuthorName(metadata.getAuthorName());
        epubRequest.setSeriesId(metadata.getSeriesId());
        epubRequest.setSeriesName(metadata.getSeriesName());
        epubRequest.setSeriesVolume(metadata.getSeriesVolume());
        epubRequest.setTags(metadata.getTags());
        epubRequest.setCreateMissingAuthor(request.getCreateMissingAuthor());
        epubRequest.setCreateMissingSeries(request.getCreateMissingSeries());
        epubRequest.setExtractCover(true);

        EPUBImportResponse epubResponse = epubImportService.importEPUB(epubRequest);

        // Convert EPUBImportResponse to FileImportResponse
        if (epubResponse.isSuccess()) {
            FileImportResponse response = FileImportResponse.success(epubResponse.getStoryId(), epubResponse.getStoryTitle(), "EPUB");
            response.setFileName(fileName);
            response.setWordCount(epubResponse.getWordCount());
            return response;
        } else {
            return FileImportResponse.error(epubResponse.getMessage(), fileName);
        }
    }

    private FileImportResponse importPDFFromSession(ZIPSession session, String fileName,
                                                    ZIPImportRequest.FileImportMetadata metadata,
                                                    ZIPImportRequest request) throws IOException {
        Path filePath = session.getTempDir().resolve(fileName);
        byte[] fileBytes = Files.readAllBytes(filePath);

        MultipartFile pdfFile = new TempMultipartFile(fileBytes, fileName, "application/pdf");

        PDFImportRequest pdfRequest = new PDFImportRequest();
        pdfRequest.setPdfFile(pdfFile);
        pdfRequest.setAuthorId(metadata.getAuthorId());
        pdfRequest.setAuthorName(metadata.getAuthorName());
        pdfRequest.setSeriesId(metadata.getSeriesId());
        pdfRequest.setSeriesName(metadata.getSeriesName());
        pdfRequest.setSeriesVolume(metadata.getSeriesVolume());
        pdfRequest.setTags(metadata.getTags());
        pdfRequest.setCreateMissingAuthor(request.getCreateMissingAuthor());
        pdfRequest.setCreateMissingSeries(request.getCreateMissingSeries());
        pdfRequest.setExtractImages(request.getExtractImages());

        return pdfImportService.importPDF(pdfRequest);
    }

    private void scheduleSessionCleanup(String sessionId) {
        Timer timer = new Timer(true);
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                cleanupSession(sessionId);
            }
        }, ZIP_SESSION_TIMEOUT_MS);
    }

    private void cleanupSession(String sessionId) {
        ZIPSession session = activeSessions.remove(sessionId);
        if (session != null) {
            try {
                deleteDirectory(session.getTempDir());
                log.info("Cleaned up ZIP session: {}", sessionId);
            } catch (Exception e) {
                log.error("Failed to cleanup ZIP session {}: {}", sessionId, e.getMessage(), e);
            }
        }
    }

    private void deleteDirectory(Path directory) throws IOException {
        if (Files.exists(directory)) {
            Files.walk(directory)
                    .sorted((a, b) -> -a.compareTo(b)) // Delete files before directories
                    .forEach(path -> {
                        try {
                            Files.delete(path);
                        } catch (IOException e) {
                            log.warn("Failed to delete file {}: {}", path, e.getMessage());
                        }
                    });
        }
    }

    private ZIPImportResponse createErrorResponse(String message) {
        ZIPImportResponse response = new ZIPImportResponse();
        response.setSuccess(false);
        response.setMessage(message);
        return response;
    }

    // Inner classes

    private static class ZIPSession {
        private final String sessionId;
        private final Path tempDir;
        private final Map<String, FileInfoDto> files;
        private final long createdAt;

        public ZIPSession(String sessionId, Path tempDir, List<FileInfoDto> fileList) {
            this.sessionId = sessionId;
            this.tempDir = tempDir;
            this.files = new HashMap<>();
            for (FileInfoDto file : fileList) {
                this.files.put(file.getFileName(), file);
            }
            this.createdAt = System.currentTimeMillis();
        }

        public Path getTempDir() {
            return tempDir;
        }

        public FileInfoDto getFileInfo(String fileName) {
            return files.get(fileName);
        }

        public boolean isExpired() {
            return System.currentTimeMillis() - createdAt > ZIP_SESSION_TIMEOUT_MS;
        }
    }

    /**
     * Temporary MultipartFile implementation for extracted files
     */
    private static class TempMultipartFile implements MultipartFile {
        private final byte[] data;
        private final String filename;
        private final String contentType;

        public TempMultipartFile(byte[] data, String filename, String contentType) {
            this.data = data;
            this.filename = filename;
            this.contentType = contentType;
        }

        @Override
        public String getName() {
            return "file";
        }

        @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 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 {
            Files.write(dest, data);
        }
    }
}
