using DocumentFormat.OpenXml;
using DocumentFormat.OpenXml.Packaging;
using DocumentFormat.OpenXml.Wordprocessing;
using System.Text.RegularExpressions;

namespace WorkFlowCheck.BL.DocumentGenerator
{
    public class SablonGenerator : IDisposable
    {
        private readonly List<OpenXmlElement> _currentRoot;

        private MemoryStream Mem { get; }
        private WordprocessingDocument Doc { get; set; }

        private List<OpenXmlElement> CurrentRoot => Repeater?.SablonRoot ?? _currentRoot;

        private SubSablon Repeater { get; set; }

        protected class SubSablon : IDisposable
        {
            private List<OpenXmlElement> SablonRootBase { get; set; }
            protected internal List<OpenXmlElement> SablonRoot { get; private set; }
            public BookmarkStart BookmarkStart { get; }
            public BookmarkEnd BookmarkEnd { get; }

            protected internal SubSablon(WordprocessingDocument doc, string key)
            {
                var bookmark = doc.MainDocumentPart.RootElement.Descendants<BookmarkStart>()
                    .FirstOrDefault(x => x.Name == key);
                //var parentBookmark = false;
                if (bookmark != null)
                {
                    SablonRoot = new List<OpenXmlElement>();
                    var nextSibling = bookmark.NextSibling();
                    if (nextSibling == null)
                        nextSibling = bookmark.Parent;
                    while (nextSibling != null)
                    {
                        var endBookmark = (nextSibling is BookmarkEnd end && end.Id == bookmark.Id.Value) ? end : null;
                        if (endBookmark == null)
                        {
                            endBookmark = nextSibling.Descendants<BookmarkEnd>().FirstOrDefault(x => x.Id == bookmark.Id.Value);
                            if (endBookmark != null)
                                SablonRoot.Add(nextSibling);
                        }
                        if (endBookmark != null)
                        {
                            bookmark.Remove();
                            endBookmark.Remove();
                            break;
                        }
                        SablonRoot.Add(nextSibling);
                        var tmpNextSibling = nextSibling.NextSibling();
                        if (tmpNextSibling == null)
                        {
                            tmpNextSibling = nextSibling.Parent;
                            if (tmpNextSibling != null && !(tmpNextSibling is Body) && !(tmpNextSibling is Document))
                                SablonRoot.Clear();
                            else
                                break;
                        }
                        nextSibling = tmpNextSibling;
                    }
                }

                if (SablonRoot != null && SablonRoot.Count > 0)
                {
                    SablonRootBase = new List<OpenXmlElement>();
                    foreach (var item in SablonRoot)
                    {
                        SablonRootBase.Add(item.CloneNode(true));
                    }
                    BookmarkStart = new BookmarkStart() { Name = "repeater", Id = "1000" };
                    BookmarkEnd = new BookmarkEnd() { Id = "1000" };
                    if (SablonRoot.First().Parent != null)
                    {
                        SablonRoot.First().InsertBeforeSelf(BookmarkStart);
                        SablonRoot.First().InsertBeforeSelf(BookmarkEnd);
                        foreach (var rootItem in SablonRoot)
                            rootItem.Remove();
                    }
                    else
                    {
                        foreach (var rootItem in SablonRoot)
                            foreach (var rootItemChildElement in rootItem.ChildElements)
                                rootItemChildElement.Remove();
                        SablonRoot.First().AppendChild(BookmarkStart);
                        SablonRoot.First().AppendChild(BookmarkEnd);
                    }
                    Reset();
                }
            }

            private void Insert()
            {
                var root = SablonRoot;
                if (root.Count == 1 && root.First() is Document)
                {
                    root = root.First().ChildElements.ToList();
                }
                for (var i = 0; i < root.Count; i++)
                {
                    if (i == 0)
                        BookmarkStart.InsertBeforeSelf(root[i]);
                    else
                        root[i - 1].InsertAfterSelf(root[i]);
                }
            }

            private void Reset()
            {
                SablonRoot.Clear();
                foreach (var item in SablonRootBase)
                {
                    SablonRoot.Add(item.CloneNode(true));
                }
            }

            protected internal void InsertAndReset()
            {
                Insert();
                Reset();
            }

            public void Dispose()
            {
                BookmarkStart?.Remove();
                BookmarkEnd?.Remove();
                SablonRootBase = null;
                SablonRoot = null;
            }
        }

        public IDisposable Repeat(string key)
        {
            Repeater = new SubSablon(Doc, key);
            return Repeater;
        }

        public SablonGenerator(byte[] content)
        {
            Mem = new MemoryStream();
            Mem.Write(content, 0, content.Length);
            try
            {
                Doc = WordprocessingDocument.Open(Mem, true, new OpenSettings()
                {
                    MarkupCompatibilityProcessSettings =
                        new MarkupCompatibilityProcessSettings(MarkupCompatibilityProcessMode.ProcessAllParts,
                            FileFormatVersions.Office2010)
                });
            }
            catch (Exception)
            {
                //if (!string.IsNullOrEmpty(Config.TryGetStringValue("SablonGeneratorErrorPath")))
                //{
                //    File.WriteAllBytes(Path.Combine(Config.GetStringValue("SablonGeneratorErrorPath"), DateTime.Now.ToString("yyyyMMddHHHmmss.content")), content);
                //}
                throw;
            }

            _currentRoot = new List<OpenXmlElement>();
            CurrentRoot.AddRange(Doc.MainDocumentPart.HeaderParts.Select(x => x.RootElement));
            CurrentRoot.Add(Doc.MainDocumentPart.RootElement);
            CurrentRoot.AddRange(Doc.MainDocumentPart.FooterParts.Select(x => x.RootElement));
        }

        public static void CheckOpenXmlDocument(byte[] content)
        {
            WordprocessingDocument doc = null;
            var mem = new MemoryStream();
            mem.Write(content, 0, content.Length);
            try
            {
                doc = WordprocessingDocument.Open(mem, true, new OpenSettings()
                {
                    MarkupCompatibilityProcessSettings =
                        new MarkupCompatibilityProcessSettings(MarkupCompatibilityProcessMode.ProcessAllParts,
                            FileFormatVersions.Office2010)
                });
            }
            catch (Exception ex)
            {
#if DEBUG
                //LoggerHelper.DiagnosticAudit.Information(DateTime.Now,
                //Common.Enums.Area.BaseSystem,
                //0,
                //string.Format(ex.ToString()),
                //new Guid().ToString());
                //LoggerHelper.DiagnosticAudit.Log.Information(ex.ToString());
#else
                //LoggerHelper.DiagnosticAudit.Information(DateTime.Now,
                //Common.Enums.Area.BaseSystem,
                //0,
                //string.Format(ex.Message),
                //Guid.Empty.ToString());
#endif
                //throw new BusinessException("Hibás vagy nem megfelelő OpenXML állomány.");
            }
            finally
            {
                mem?.Dispose();
                doc?.Dispose();
            }
        }

        public void Generate(Dictionary<string, string> propertyDictionary)
        {
            ReplaceBookmarks(propertyDictionary);
            if (Repeater?.SablonRoot != null) Repeater.InsertAndReset();
        }

        public void SimpleReplace(string key, string value)
        {
            value = value == null ? "" : value;

            foreach (var headerPart in Doc.MainDocumentPart.HeaderParts)
                SimpleReplace2(headerPart.Header, key, value);
            SimpleReplace2(Doc.MainDocumentPart.Document.Body, key, value);
        }

        protected void SimpleReplace(OpenXmlCompositeElement target, string key, string value)
        {
            var keyValue = key.Replace("#", "");
            var texts = target.Descendants<Text>();
            Text start = null;
            Text keyValueText = null;
            foreach (var text in texts)
                if (text.Text == "#" && start == null)
                    start = text;
                else if (text.Text == "#" && start != null && keyValueText != null)
                {
                    var end = text;
                    keyValueText.Text = value;
                    start.Remove();
                    end.Remove();
                    start = null;
                    keyValueText = null;
                }
                else if (start != null && text.Text.ToUpper().Contains(keyValue.ToUpper()))
                    keyValueText = text;
                else if (text.Text.ToUpper().Contains(key.ToUpper()))
                {
                    start = null;
                    keyValueText = null;
                    text.Text = Regex.Replace(text.Text, key, value, RegexOptions.IgnoreCase);
                }
                else
                {
                    start = null;
                    keyValueText = null;
                }
        }

        protected void SimpleReplace2(OpenXmlCompositeElement target, string key, string value)
        {
            var texts = target.Descendants<Text>();
            Text start = null;
            var tokens = new List<Text>();
            string tokenText = "";

            foreach (var text in texts)
            {
                if (text.Text == "#" && start == null)
                {
                    start = text;
                    tokenText += text.Text;
                }
                else if (text.Text == "#" && start != null)
                {
                    var end = text;
                    tokenText += text.Text;

                    if (tokenText.ToUpper() == key.ToUpper())
                    {
                        start.Text = value;
                        foreach (var token in tokens)
                        {
                            token.Remove();
                        }
                        tokens.Clear();
                        end.Remove();
                        start = null;
                        tokenText = "";
                    }
                    else
                    {
                        start = null;
                        tokens.Clear();
                        tokenText = "";
                        end = null;
                    }
                }
                else if (text.Text.Contains('#') && text.Text.ToUpper() == key.ToUpper())
                {
                    text.Text = value;
                }
                else if (text.Text.Contains('#'))
                {

                }
                else if (start != null)
                {
                    tokens.Add(text);
                    tokenText += text.Text;
                }
            }
        }

        public void SimpleReplaceForCV(string key, string value)
        {
            value = value == null ? "" : value;

            SimpleReplaceForCV(Doc.MainDocumentPart.Document.Body, key, value);
        }

        protected void SimpleReplaceForCV(OpenXmlCompositeElement target, string key, string value)
        {
            var keyValue = key.Replace("#", "");
            var texts = target.Descendants<Text>();
            Text start = null;
            Text keyValueText = null;
            var keyindox = "";
            foreach (var text in texts)
            {
                if (text.Text.Contains(key))
                {
                    text.Text = text.Text.Replace(key, value);
                    break;
                }

                if (text.Text.Contains(keyValue + "#") && start != null)
                {
                    start.Text = "";
                    text.Text = text.Text.Replace(keyValue + "#", value);
                    break;
                }

                if ((text.Text == "#" || (text.Text.Contains("#") && text.Text.Count(x => x == '#') == 1)) && start == null)
                {
                    start = text;
                    keyindox = start.Text;
                }
                else if (start != null && (text.Text == "#" || text.Text.Contains("#")))
                {
                    for (int i = texts.ToList().IndexOf(start) + 1; i < texts.ToList().IndexOf(text) + 1; i++)
                    {
                        if (texts.ToList()[i].Text.Contains(key))
                        {
                            texts.ToList()[i].Text = texts.ToList()[i].Text.Replace(key, value);
                            break;
                        }

                        keyindox = keyindox + texts.ToList()[i].Text;
                    }
                    keyindox = keyindox.Replace("#", "");
                    if (keyindox == keyValue || keyindox.Replace(" ", "") == keyValue)
                    {
                        for (int i = texts.ToList().IndexOf(start) + 1; i < texts.ToList().IndexOf(text); i++)
                        {
                            texts.ToList()[i].Text = "";
                        }
                        start.Text = "";
                        text.Text = value;
                    }
                    if (keyindox.Contains(keyValue))
                    {
                        for (int i = texts.ToList().IndexOf(start) + 1; i < texts.ToList().IndexOf(text); i++)
                        {
                            texts.ToList()[i].Text = "";
                        }
                        start.Text = "";
                        text.Text = keyindox.Replace(keyValue, value);
                    }
                    start = null;
                    keyindox = "";
                }
            }

        }

        private void ReplaceBookmarks(Dictionary<string, string> propertyDictionary)
        {
            var bookmarkEndList = new List<BookmarkEnd>();
            foreach (var item in CurrentRoot)
            {
                var bookmarkEnds = item.Descendants<BookmarkEnd>();
                bookmarkEndList.AddRange(bookmarkEnds.ToList());
            }

            foreach (var docContent in CurrentRoot)
            {
                var bookmarkStarts = docContent.Descendants<BookmarkStart>();

                var bookmarkStartList = bookmarkStarts.ToList();
                if (docContent is BookmarkStart)
                    bookmarkStartList.Add(docContent as BookmarkStart);

                for (int k = 0; k < bookmarkStartList.Count; k++)
                {
                    Run bookmarkRun = bookmarkStartList[k].NextSibling<Run>();
                    if (bookmarkRun != null)
                    {
                        var bookmarkText = bookmarkRun.GetFirstChild<Text>().InnerText;
                        if (propertyDictionary.ContainsKey(bookmarkText))
                        {
                            var itemValue = propertyDictionary[bookmarkText];

                            bookmarkRun.GetFirstChild<Text>().Text = itemValue ?? string.Empty;
                            var id = bookmarkStartList[k].Id.Value;
                            bookmarkStartList[k].Remove();
                            var end = bookmarkEndList.Find(x => x.Id == id);
                            end.Remove();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Táblázat generálás (a keyinrow paramétert keresi a táblázat első sorában (nem fejléc), ezt a sort kitörli, és helyére szúrja be az új sorokat
        /// amit az első paraméterben kap meg
        /// </summary>
        /// <param name="rowsAndCells">sorok és benne az egyes cellák tartalma</param>
        /// <param name="keyInRow">első (nem fejléc) sorában lévő kulcs, ez alapján azonosítjuk be a táblázatot</param>
        /// <param name="deleteEmptyRow">ha igaz, a táblázat összes meglévő sorát törli</param>
        public void SetTable_Old(List<List<string>> rowsAndCells, string keyInRow, bool deleteEmptyRow = false)
        {
            Body bod = Doc.MainDocumentPart.Document.Body;
            var table = bod.Descendants<Table>().Where(tbl => tbl.InnerText.Contains(keyInRow)).FirstOrDefault();
            if (table != null)
            {
                if (deleteEmptyRow)
                {
                    var firstRow = table.ChildElements.Where(x => x is TableRow).FirstOrDefault();
                    var deletedRows = table.ChildElements.Where(x => x is TableRow).ToList();
                    deletedRows.Remove(firstRow);
                    foreach (var item in deletedRows)
                        table.RemoveChild(item);
                }
                else
                {
                    var deletedRow = table.ChildElements.Where(x => x is TableRow).LastOrDefault();
                    table.RemoveChild<TableRow>(deletedRow as TableRow);
                }

                foreach (var row in rowsAndCells)
                {
                    List<OpenXmlElement> cells = new List<OpenXmlElement>();

                    foreach (var cell in row)
                    {
                        var tableCell = new TableCell(new Paragraph(new Run(new Text(cell))));
                        cells.Add(tableCell);
                    }

                    table.Append(new TableRow(cells));
                }
            }
        }
        public void SetTable(List<List<string>> rowsAndCells, string keyInRow, bool deleteEmptyRow = false)
        {
            Body bod = Doc.MainDocumentPart.Document.Body;
            var table = bod.Descendants<Table>().Where(tbl => tbl.InnerText.Contains(keyInRow)).FirstOrDefault();
            if (table != null)
            {
                TableRow formatRow = null;

                // Megkeressük az első sort és elmentjük a formázást
                if (deleteEmptyRow)
                {
                    var firstRow = table.ChildElements.OfType<TableRow>().FirstOrDefault();
                    if (firstRow != null)
                    {
                        formatRow = (TableRow)firstRow.CloneNode(true); // Az első sor másolása formázással
                        var deletedRows = table.ChildElements.OfType<TableRow>().ToList();
                        foreach (var item in deletedRows)
                            table.RemoveChild(item);
                    }
                }
                else
                {
                    var deletedRow = table.ChildElements.OfType<TableRow>().LastOrDefault();
                    if (deletedRow != null)
                    {
                        formatRow = (TableRow)deletedRow.CloneNode(true); // Az utolsó sor másolása formázással
                        table.RemoveChild(deletedRow);
                    }
                }

                // Új sorok beszúrása a mentett formázással
                foreach (var row in rowsAndCells)
                {
                    List<OpenXmlElement> cells = new List<OpenXmlElement>();

                    for (int i = 0; i < row.Count; i++)
                    {
                        var cellText = row[i].Replace("#", "");

                        // Ha van formázási minta, alkalmazzuk
                        TableCell newCell;
                        if (formatRow != null && i < formatRow.Elements<TableCell>().Count())
                        {
                            var formatCell = formatRow.Elements<TableCell>().ElementAt(i);
                            var clonedCell = (TableCell)formatCell.CloneNode(true);
                            var para = clonedCell.Descendants<Paragraph>().FirstOrDefault();

                            // Ha van beágyazott szöveg, frissítjük a tartalmát
                            if (para != null)
                            {
                                var run = para.Descendants<Run>().FirstOrDefault();
                                if (run != null)
                                {
                                    run.RemoveAllChildren<Text>();
                                    run.AppendChild(new Text(cellText));
                                }
                            }

                            newCell = clonedCell;
                        }
                        else
                        {
                            // Alapértelmezett cella, ha nincs formázási minta
                            newCell = new TableCell(new Paragraph(new Run(new Text(cellText))));
                        }

                        cells.Add(newCell);
                    }

                    table.Append(new TableRow(cells));
                }
            }
        }

        public byte[] CloseAndGetDocument()
        {
            Doc.Dispose();
            Doc = null;
            return Mem.ToArray();
        }

        public void Dispose()
        {
            Doc?.Dispose();
            Mem.Dispose();
        }

        /// <summary>
        /// Hozzáfűzi a már megnyitott fájlhoz a paraméterként kapott dokumentum tartalmát.
        /// </summary>
        /// <param name="document"></param>
        /// <param name="addPageBreak"></param>
        public void AppendDocument(byte[] document, bool addPageBreak = true)
        {
            var mainPart = Doc.MainDocumentPart;
            string altChunkId = "AltChunk_" + Guid.NewGuid().ToString().Replace("-", "");

            var chunk = mainPart.AddAlternativeFormatImportPart(AlternativeFormatImportPartType.WordprocessingML, altChunkId);
            chunk.FeedData(new MemoryStream(document));

            // oldaltörés, ha kell
            if (addPageBreak)
            {
                mainPart.Document.Body.AppendChild(new Paragraph(new Run(new Break() { Type = BreakValues.Page })));
            }

            mainPart.Document.Body.AppendChild(new AltChunk() { Id = altChunkId });

            mainPart.Document.Save();
        }
    }
}
