import com.aspose.email.FileFormatVersion;
import com.aspose.email.FolderInfo;
import com.aspose.email.MapiMessage;
import com.aspose.email.MessageInfo;
import com.aspose.email.PersonalStorage;
import com.aspose.email.MailMessage;
import com.aspose.email.MailConversionOptions;
import com.aspose.email.MboxrdStorageWriter;
import com.aspose.email.MapiContact;
import com.aspose.email.MapiItemType;
import com.aspose.email.ContactSaveFormat;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class Convert {
    private static PersonalStorage sourceStorage;

    public static void main(String[] args) {
        if (args.length < 2) {
            System.err.println("Usage: java -cp <classpath> Convert <input.ost> <output.pst/zip> [format]");
            System.exit(1);
        }

        String inputPath = args[0];
        String outputPath = args[1];
        String format = (args.length >= 3) ? args[2].toLowerCase() : "pst";
        boolean combineVcf = (args.length >= 4) && args[3].equalsIgnoreCase("true");

        System.out.println("Starting manual copy conversion of OST: " + inputPath + " to format: " + format.toUpperCase());
        long startTime = System.currentTimeMillis();

        try {
            // Delete existing output file if it exists
            File outFile = new File(outputPath);
            if (outFile.exists()) {
                System.out.println("Destination file already exists. Deleting " + outputPath + "...");
                if (outFile.delete()) {
                    System.out.println("Deleted existing file successfully.");
                } else {
                    System.err.println("Warning: Failed to delete existing file.");
                }
            }

            // Load the source OST file
            System.out.println("Loading OST file...");
            sourceStorage = PersonalStorage.fromFile(inputPath);

            if (format.equals("mbox")) {
                // Export folders to a temporary directory of .mbox files, then zip them
                File tempDir = new File("mbox_temp_" + System.currentTimeMillis());
                if (!tempDir.exists()) {
                    tempDir.mkdirs();
                }
                
                System.out.println("Exporting folder structure, email messages, and contacts...");
                exportFoldersToMbox(sourceStorage.getRootFolder(), "", tempDir, combineVcf);
                
                // If combineVcf was requested, merge all individual VCFs in the contacts subdirs
                if (combineVcf) {
                    System.out.println("Merging contact VCF files into a single contacts.vcf...");
                    mergeVcfFiles(tempDir);
                }
                
                System.out.println("Packaging MBOX files and VCards into ZIP archive...");
                zipDirectory(tempDir, outputPath);
                
                System.out.println("Cleaning up temporary MBOX/VCard files...");
                cleanDirectory(tempDir);
                tempDir.delete();
                
                long duration = System.currentTimeMillis() - startTime;
                System.out.println("Conversion completed successfully in " + (duration / 1000.0) + " seconds.");
            } else {
                // Default PST format
                System.out.println("Creating destination PST file...");
                try (PersonalStorage destPst = PersonalStorage.create(outputPath, FileFormatVersion.Unicode)) {
                    System.out.println("Copying folder structure and messages...");
                    copyFolders(sourceStorage.getRootFolder(), destPst.getRootFolder());
                    
                    long duration = System.currentTimeMillis() - startTime;
                    System.out.println("Conversion completed successfully in " + (duration / 1000.0) + " seconds.");
                }
            }

            sourceStorage.close();
            System.exit(0);
        } catch (Exception e) {
            System.err.println("Error during conversion: " + e.getMessage());
            e.printStackTrace();
            if (sourceStorage != null) {
                try {
                    sourceStorage.close();
                } catch (Exception ex) {
                    // Ignore
                }
            }
            System.exit(1);
        }
    }

    private static void copyFolders(FolderInfo sourceFolder, FolderInfo destParentFolder) {
        for (FolderInfo subFolder : sourceFolder.getSubFolders()) {
            String folderName = subFolder.getDisplayName();
            System.out.println("Processing Folder: " + folderName);
            
            // Recreate folder in destination
            FolderInfo newDestFolder = destParentFolder.addSubFolder(folderName);
            
            // Copy messages
            int copiedCount = 0;
            for (MessageInfo msgInfo : subFolder.enumerateMessages()) {
                try {
                    MapiMessage msg = sourceStorage.extractMessage(msgInfo);
                    newDestFolder.addMessage(msg);
                    copiedCount++;
                    if (copiedCount % 100 == 0) {
                        System.out.println("  Copied " + copiedCount + " messages...");
                    }
                } catch (Exception ex) {
                    System.err.println("  Warning: Failed to copy message '" + msgInfo.getSubject() + "': " + ex.getMessage());
                }
            }
            if (copiedCount > 0) {
                System.out.println("  Successfully copied " + copiedCount + " messages to folder: " + folderName);
            }
            
            // Recursively copy subfolders
            copyFolders(subFolder, newDestFolder);
        }
    }

    private static void exportFoldersToMbox(FolderInfo sourceFolder, String currentPath, File tempDir, boolean combineVcf) {
        for (FolderInfo subFolder : sourceFolder.getSubFolders()) {
            String folderName = subFolder.getDisplayName();
            String fullPath = currentPath.isEmpty() ? folderName : currentPath + " - " + folderName;
            System.out.println("Processing Folder: " + folderName);
            
            boolean isContactsFolder = folderName.toLowerCase().contains("contacts");

            // Skip other non-email folders
            if (folderName.toLowerCase().contains("calendrier") || 
                folderName.toLowerCase().contains("journal") || 
                folderName.toLowerCase().contains("tâches") ||
                folderName.toLowerCase().contains("notes")) {
                System.out.println("  Skipping non-email/non-contact folder: " + folderName);
                
                // Still process children in case there are nested folders
                exportFoldersToMbox(subFolder, fullPath, tempDir, combineVcf);
                continue;
            }

            if (isContactsFolder) {
                System.out.println("  Processing contacts in folder: " + folderName);
                String safeSubDirName = "Contacts - " + folderName.replaceAll("[\\\\/:*?\"<>|]", "_");
                File contactsDir = new File(tempDir, safeSubDirName);
                if (!contactsDir.exists()) {
                    contactsDir.mkdirs();
                }

                int contactCount = 0;
                for (MessageInfo msgInfo : subFolder.enumerateMessages()) {
                    try {
                        MapiMessage msg = sourceStorage.extractMessage(msgInfo);
                        if (msg.getSupportedType() == MapiItemType.Contact) {
                            MapiContact contact = (MapiContact) msg.toMapiMessageItem();
                            
                            // Determine a suitable name for the .vcf file
                            String displayName = null;
                            if (contact.getNameInfo() != null) {
                                displayName = contact.getNameInfo().getDisplayName();
                            }
                            if (displayName == null || displayName.trim().isEmpty()) {
                                if (contact.getElectronicAddresses() != null && 
                                    contact.getElectronicAddresses().getEmail1() != null) {
                                    displayName = contact.getElectronicAddresses().getEmail1().getEmailAddress();
                                }
                            }
                            if (displayName == null || displayName.trim().isEmpty()) {
                                displayName = "Contact_" + (contactCount + 1);
                            }

                            String safeContactName = displayName.replaceAll("[\\\\/:*?\"<>|]", "_") + ".vcf";
                            File vcfFile = new File(contactsDir, safeContactName);
                            
                            // Check for duplicates
                            int index = 1;
                            while (vcfFile.exists()) {
                                String nameWithIndex = displayName.replaceAll("[\\\\/:*?\"<>|]", "_") + "_" + index + ".vcf";
                                vcfFile = new File(contactsDir, nameWithIndex);
                                index++;
                            }

                            contact.save(vcfFile.getAbsolutePath(), ContactSaveFormat.VCard);
                            contactCount++;
                        }
                    } catch (Exception ex) {
                        System.err.println("  Warning: Failed to export contact: " + ex.getMessage());
                    }
                }

                if (contactCount > 0) {
                    System.out.println("  Successfully exported " + contactCount + " contacts to folder: " + folderName);
                } else {
                    contactsDir.delete();
                }

                // Recursively walk subfolders
                exportFoldersToMbox(subFolder, fullPath, tempDir, combineVcf);
                continue;
            }

            // Otherwise, it's an email folder. Export to MBOX...
            int copiedCount = 0;
            String safeFileName = fullPath.replaceAll("[\\\\/:*?\"<>|]", "_") + ".mbox";
            File mboxFile = new File(tempDir, safeFileName);
            
            MailConversionOptions options = new MailConversionOptions();
            MboxrdStorageWriter writer = null;
            
            for (MessageInfo msgInfo : subFolder.enumerateMessages()) {
                try {
                    if (writer == null) {
                        writer = new MboxrdStorageWriter(mboxFile.getAbsolutePath(), false);
                    }
                    MapiMessage msg = sourceStorage.extractMessage(msgInfo);
                    MailMessage mailMsg = msg.toMailMessage(options);
                    writer.writeMessage(mailMsg);
                    copiedCount++;
                    if (copiedCount % 100 == 0) {
                        System.out.println("  Exported " + copiedCount + " messages...");
                    }
                } catch (Exception ex) {
                    System.err.println("  Warning: Failed to export message '" + msgInfo.getSubject() + "': " + ex.getMessage());
                }
            }
            
            if (writer != null) {
                try {
                    writer.close();
                } catch (Exception ex) {
                    // Ignore
                }
            }
            
            if (copiedCount > 0) {
                System.out.println("  Successfully copied " + copiedCount + " messages to folder: " + folderName);
            } else {
                // Delete empty mbox file if created
                if (mboxFile.exists()) {
                    mboxFile.delete();
                }
            }
            
            // Recursively walk subfolders
            exportFoldersToMbox(subFolder, fullPath, tempDir, combineVcf);
        }
    }

    /**
     * Walk all contact subdirectories, read each .vcf, and write them all
     * into a single contacts.vcf at the root of tempDir.
     * Then delete the now-merged individual files and empty subdirs.
     */
    private static void mergeVcfFiles(File tempDir) throws Exception {
        File combinedVcf = new File(tempDir, "contacts.vcf");
        try (FileOutputStream fos = new FileOutputStream(combinedVcf)) {
            for (File child : tempDir.listFiles()) {
                if (child.isDirectory() && child.getName().startsWith("Contacts")) {
                    for (File vcf : child.listFiles()) {
                        if (vcf.isFile() && vcf.getName().endsWith(".vcf")) {
                            byte[] data = java.nio.file.Files.readAllBytes(vcf.toPath());
                            fos.write(data);
                            // Ensure each vCard ends with a newline separator
                            if (data.length > 0 && data[data.length - 1] != '\n') {
                                fos.write('\n');
                            }
                        }
                    }
                    // Remove the now-merged individual VCF directory
                    cleanDirectory(child);
                    child.delete();
                }
            }
        }
        System.out.println("  Merged VCF written: " + combinedVcf.getName());
    }

    private static void zipDirectory(File dir, String zipFilePath) throws Exception {
        try (FileOutputStream fos = new FileOutputStream(zipFilePath);
             ZipOutputStream zos = new ZipOutputStream(fos)) {
            zipFolder(dir, dir, zos);
        }
    }

    private static void zipFolder(File rootDir, File currentDir, ZipOutputStream zos) throws Exception {
        File[] files = currentDir.listFiles();
        if (files != null) {
            byte[] buffer = new byte[8192];
            for (File file : files) {
                if (file.isDirectory()) {
                    zipFolder(rootDir, file, zos);
                } else {
                    String relativePath = rootDir.toURI().relativize(file.toURI()).getPath();
                    try (FileInputStream fis = new FileInputStream(file)) {
                        ZipEntry ze = new ZipEntry(relativePath);
                        zos.putNextEntry(ze);
                        int len;
                        while ((len = fis.read(buffer)) > 0) {
                            zos.write(buffer, 0, len);
                        }
                        zos.closeEntry();
                    }
                }
            }
        }
    }

    private static void cleanDirectory(File dir) {
        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    cleanDirectory(file);
                }
                file.delete();
            }
        }
    }
}
