#nullable enable

using System;
using System.Collections.ObjectModel;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Platform.Storage;
using Avalonia.Themes.Fluent;
using DialogHostAvalonia;
using ImageMagick;
using MigraDoc.DocumentObjectModel;
using MigraDoc.Rendering;
using PdfSharp.Fonts;
using PdfSharp.Pdf.IO;
using PdfSharp.Snippets.Font;
using ReceiptPDFBuilder.Interfaces;
using ReceiptPDFBuilder.Models;

namespace ReceiptPDFBuilder.ViewModels;

class MainViewModel : BaseViewModel, IFontResolver
{
    private string _baseDir;
    private bool _isCreatingPDF;
    private string _createPDFLog;
    private string _workingFolder;

    private string _reportTitle;
    private ObservableCollection<ReportFile> _reportFiles;

    public MainViewModel(IChangeViewModel viewModelChanger) : base(viewModelChanger)
    {
        _baseDir = Path.GetDirectoryName(Environment.ProcessPath) ?? "";
        _isCreatingPDF = false;
        _createPDFLog = "Ready to create PDF! Choose a folder to begin...";
        _workingFolder = "";
        _reportFiles = new ObservableCollection<ReportFile>();
        _reportFiles.CollectionChanged += ( sender, e ) => { NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled)); };
        _reportTitle = "";
    }

    public string ReportTitle
    {
        get => _reportTitle;
        set { _reportTitle = value; NotifyPropertyChanged(); NotifyPropertyChanged(nameof(IsTitleBoxVisible)); }
    }

    public bool IsTitleBoxVisible
    {
        get => !string.IsNullOrWhiteSpace(_workingFolder);
    }

    public bool IsCreatingPDF
    {
        get => _isCreatingPDF;
        set { _isCreatingPDF = value; NotifyPropertyChanged(); NotifyPropertyChanged(nameof(IsCreatePDFButtonEnabled)); }
    }

    public bool IsCreatePDFButtonEnabled
    {
        get => !_isCreatingPDF && _reportFiles.Count > 0;
    }

    public string CreatePDFLog
    {
        get => _createPDFLog;
        set { _createPDFLog = value; NotifyPropertyChanged(); }
    }

    public ObservableCollection<ReportFile> ReportFiles
    {
        get => _reportFiles;
        set { _reportFiles = value; NotifyPropertyChanged(); }
    }

    private void LogInfo(string message, params object[]? arguments)
    {
        var timestamp = string.Format("[{0:s}]", DateTime.Now);
        Console.WriteLine(timestamp + " " + message, arguments);
        CreatePDFLog += Environment.NewLine + string.Format(message, arguments ?? []);
    }

    public async void ChooseFolder()
    {
        var topLevel = TopLevelGrabber?.GetTopLevel();
        if (topLevel is not null)
        {
            var folders = await topLevel.StorageProvider.OpenFolderPickerAsync(new FolderPickerOpenOptions()
            {
                Title = "Pick a folder of files...",
                AllowMultiple = false,
            });
            if (folders.Count == 1)
            {
                var folder = folders[0];
                LogInfo("Chosen folder: " + folder.Path.LocalPath);
                if (Directory.Exists(folder.Path.LocalPath))
                {
                    _workingFolder = folder.Path.LocalPath;
                    NotifyPropertyChanged(nameof(IsTitleBoxVisible));
                    // TODO: Scan folder for saved info from previous reports and reload if needed
                    // Scan folder for files and display in DataGrid
                    var filePaths = Directory.GetFiles(_workingFolder);
                    filePaths.Sort();
                    foreach (var filePath in filePaths)
                    {
                        if (!filePath.Contains(".DS_Store"))
                        {
                            // TODO: if date in file name, pull out that date instead
                            ReportFiles.Add(new ReportFile()
                            {
                                Title = Path.GetFileName(filePath),
                                ReceiptDateTime = File.GetCreationTime(filePath),
                                Notes = "",
                                FilePath = filePath,
                            });
                        }
                    }
                }
            }
        }
    }

    public async void RemoveFile(ReportFile file)
    {
        var result = await DialogHost.Show(new WarningDeleteItemModel(file));
        if (result != null && (bool)result)
        {
            var idx = ReportFiles.IndexOf(file);
            if (idx != -1)
            {
                ReportFiles.RemoveAt(idx);
            }
        }
    }

    public async void EditFileProperties(ReportFile file)
    {
        var result = await DialogHost.Show(new EditFileViewModel(file, ViewModelChanger));
        if (result != null && result is ReportFile updatedData)
        {
            file.Title = updatedData.Title;
            file.ReceiptDateTime = updatedData.ReceiptDateTime;
            file.Notes = updatedData.Notes;
        }
    }

    public async void BuildPDF()
    {
        try
        {
            // TODO: use already found files and information
            await Task.Run(() => CreatePDF(_workingFolder));
        } catch (Exception e)
        {
            LogInfo("PDF process failed! Reason: " + e.Message);
            if (e.StackTrace != null)
            {
                LogInfo(e.StackTrace);
            }
            if (e.InnerException != null)
            {
                LogInfo("Inner exception: " + e.InnerException.Message);
                if (e.InnerException.StackTrace != null)
                {
                    LogInfo(e.InnerException.StackTrace);
                }
            }
        }
    }

    public byte[]? GetFont(string faceName)
    {
        LogInfo(string.Format("Loading font {0}", faceName));
        if (faceName == "Noto Sans JP")
        {
            var path = Path.Combine(_baseDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf");
            if (!File.Exists(path))
            {
                path = Path.Combine(_baseDir, "../Resources/Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-Regular.ttf");
            }
            return File.ReadAllBytes(path);
        }
        if (faceName == "Noto Sans JP Bold")
        {
            var path = Path.Combine(_baseDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf");
            if (!File.Exists(path))
            {
                path = Path.Combine(_baseDir, "../Resources/Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf");
            }
            return File.ReadAllBytes(Path.Combine(_baseDir, "Assets/Fonts/Noto_Sans_JP/static/NotoSansJP-SemiBold.ttf"));
        }
        return null;
    }

    public FontResolverInfo? ResolveTypeface(string familyName, bool bold, bool italic)
    {
        // LogInfo(string.Format("Resolving font name {0}", familyName));
        if (familyName == "Noto Sans JP")
        {
            if (bold)
            {
                return new FontResolverInfo(familyName + " Bold");
            }
            return new FontResolverInfo(familyName);
        }
        return null;
    }

    // https://forum.pdfsharp.net/viewtopic.php?f=2&t=1025
    private async Task CreatePDF(string folderPath)
    {
        // TODO: calculate needed width for images based on page width and margins and all that?
        // TODO: resize (non-HEIC) images for smaller size...?
        IsCreatingPDF = true;
        var pdfDoc = new Document();
        var outputFileName = "MyReceipts.pdf";
        var folderName = new DirectoryInfo(folderPath).Name;
        const int imageWidth = 425;
        if (folderName.Contains('-'))
        {
            // see if year/month format
            var parts = folderName.Split('-');
            if (parts[0].Length == 4 &&
                parts[1].Length <= 2 &&
                int.TryParse(parts[0], out int year) && int.TryParse(parts[1], out int month))
            {
                outputFileName = string.Format("{0} {1} Receipts.pdf", 
                    CultureInfo.CurrentCulture.DateTimeFormat.GetMonthName(month), 
                    year);
                LogInfo("Auto-changed output file name to " + outputFileName);
            }
        }
        var section = pdfDoc.AddSection();
        section.PageSetup.PageFormat = PageFormat.Letter;
        section.PageSetup.PageWidth = "8.5in";
        section.PageSetup.PageHeight = "11in";
        section.PageSetup.TopMargin = "0.5in";
        section.PageSetup.RightMargin = "0.5in";
        section.PageSetup.BottomMargin = "0.5in";
        section.PageSetup.LeftMargin = "0.5in";
        // setup footer for page number
        var footerPar = new Paragraph();
        footerPar.Format.Alignment = ParagraphAlignment.Center;
        footerPar.Format.Font.Size = 10;
        footerPar.AddText("--Page ");
        footerPar.AddPageField();
        footerPar.AddText(" of ");
        footerPar.AddNumPagesField();
        footerPar.AddText("--");
        footerPar.AddLineBreak();
        footerPar.AddText("Report generated on " + DateTime.Now.ToString("f"));
        section.Footers.Primary.Add(footerPar);
        //
        var files = Directory.GetFiles(folderPath);
        files.Sort();
        GlobalFontSettings.FontResolver = this;
        GlobalFontSettings.FallbackFontResolver = new FailsafeFontResolver();
        var hasAddedData = false;
        for (var i = 0; i < files.Length; i++)
        {
            var file = files[i];
            var fileName = Path.GetFileName(file);
            if (fileName == ".DS_Store" || fileName == outputFileName)
            {
                continue;
            }
            if (i > 0 && hasAddedData)
            {
                section.AddPageBreak();
            }
            var imageTitlePar = section.AddParagraph();
            imageTitlePar.Format.Alignment = ParagraphAlignment.Center;
            imageTitlePar.Format.Font.Size = 12;
            imageTitlePar.Format.Font.Bold = true;
            imageTitlePar.Format.Font.Name = "Noto Sans JP"; // has english letters in it, too
            imageTitlePar.AddText(fileName);
            section.AddParagraph(); // add empty line for spacing
            // now add the image
            var isPDF = fileName.EndsWith(".pdf");
            var isHEIC = fileName.EndsWith(".HEIC") || fileName.EndsWith(".heic");
            if (isHEIC)
            {
                var convertedDir = Path.Combine(folderPath, "converted");
                if (!Directory.Exists(convertedDir))
                {
                    Directory.CreateDirectory(convertedDir);
                }
                var info = new FileInfo(file);
                using var mImage = new MagickImage(info.FullName);
                // Save frame as jpg
                var outputPath = Path.Combine(convertedDir, info.Name + ".jpg");
                mImage.Quality = 80;
                mImage.Scale((uint)Math.Floor(mImage.Width * 0.5), (uint)Math.Floor(mImage.Height * 0.5));
                await mImage.WriteAsync(outputPath);
                fileName = Path.Combine("Converted", info.Name + ".jpg");
                LogInfo(string.Format("Converted HEIC image to JPEG; fileName is now {0}", fileName));
            }
            var paragraph = section.AddParagraph();
            paragraph.Format.Alignment = ParagraphAlignment.Center;
            var image = paragraph.AddImage(fileName);
            image.LockAspectRatio = true;
            image.Width = imageWidth; // can't be too wide now...not sure why...maybe due to margins...
            LogInfo(string.Format("Added image: {0}", fileName));
            if (isPDF)
            {
                // add other PDF pages
                // see: https://stackoverflow.com/a/65091204/3938401
                var pdfFileToAdd = PdfReader.Open(file);
                imageTitlePar.AddText(string.Format(" (PDF with {0} page{1}) ", 
                    pdfFileToAdd.PageCount, 
                    pdfFileToAdd.PageCount == 1 ? "" : "s"));
                for (var j = 2; j <= pdfFileToAdd.PageCount; j++)
                {
                    section.AddPageBreak();
                    paragraph = section.AddParagraph();
                    paragraph.Format.Alignment = ParagraphAlignment.Center;
                    image = paragraph.AddImage(file + "#" + j);
                    image.LockAspectRatio = true;
                    image.Width = imageWidth;
                }
            }
            hasAddedData = true;
        }
        var pdfRenderer = new PdfDocumentRenderer
        {
            Document = pdfDoc,
            WorkingDirectory = folderPath
        };
        LogInfo("Rendering document...");
        pdfRenderer.RenderDocument();
        string filename = Path.Join(folderPath, outputFileName);
        LogInfo("Saving document to disk...");
        pdfRenderer.PdfDocument.Save(filename);
        LogInfo("Saved PDF output to: " + filename);
        IsCreatingPDF = false;
        return;
    }
}