using ClosedXML.Excel;
using CloudBuilder.Core.DatabaseAccessor.Entity;
using CloudBuilder.Core.Extensions;
using CloudBuilder.Core.Service;
using DocumentFormat.OpenXml.Spreadsheet;
using DocumentFormat.OpenXml;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.SqlTypes;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Diagnostics;

namespace CloudBuilder.Core.Report
{
    public class DataToExcelHelper<T>
    {
        private readonly IApplicationService service;
        public delegate object DataValueFormatOperation(string functionName, object value);
        public delegate void CellTypeFormatOperation(IXLWorksheet xLWorksheet, string functionName, object cellValue, int rowIndex, int ColIndex, T[] datas);
        public delegate void XLWorksheetOperation(IXLWorksheet xLWorksheet, T[] datas);
        public DataToExcelHelper(IApplicationService service)
        {
            this.service = service;
        }

        public IActionResult ToExcel(T[] datas)
        {
            using (var excel = new XLWorkbook())
            {
                IXLWorksheet worksheet = GetExcelWorksheet(excel, datas);

                using (MemoryStream stream = new MemoryStream())
                {
                    excel.SaveAs(stream);
                    return new FileStreamResult(stream,
                        "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                        );
                }
            }
        }

        public IXLWorksheet GetExcelWorksheet(XLWorkbook excel, T[] datas)
        {
            string tableName = typeof(T).GetCustomAttributeValue<TableAttribute, string>(x => x.Name);

            List<TemplateHeader> headers = new List<TemplateHeader>();
            PropertyInfo[] pArray = typeof(T).GetProperties();

            string format = null;
            foreach (PropertyInfo p in pArray)
            {
                object value = p.GetType();
                format = string.Empty;
                if (value is DateTime || p.PropertyType.FullName.ToLower().Contains("datetime"))
                {
                    format = TemplateHeader.DateTimeFormat1;
                }
                else if (value is int || p.PropertyType.FullName.ToLower().Contains("int"))
                {
                    format = TemplateHeader.IntFormat1;
                }
                else if (value is decimal || p.PropertyType.FullName.ToLower().Contains("decimal"))
                {
                    format = TemplateHeader.DecimalFormat1;
                }

                //DatabaseGeneratedOption option = typeof(StockOrderEntity).GetCustomAttributeValue<DatabaseGeneratedAttribute, DatabaseGeneratedOption>(x => x.DatabaseGeneratedOption, p.Name);
                //if (option == DatabaseGeneratedOption.Identity)
                //{
                //    continue;
                //}

                string displayName = typeof(T).GetCustomAttributeValue<ColumnAttribute, string>(x => x.Name, p.Name);
                if (string.IsNullOrEmpty(displayName)) continue;

                headers.Add(new TemplateHeader
                {
                    DisplayName = displayName,
                    BindingName = p.Name,
                    Format = format,
                    Width = TemplateHeader.WidthAutoFit
                });
            }

            return PackWorksheet(excel, tableName, tableName, null, headers.ToArray(), datas, null);
        }

        public IActionResult ToExcel(string worksheetName, string title, TemplateHeader[] headers, T[] datas)
        {
            return ToExcel(worksheetName, title, null, headers, datas, null);
        }

        public IActionResult ToExcel(string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas)
        {
            return ToExcel(worksheetName, title, subTitle, headers, datas, null);
        }

        public IActionResult ToExcel(string worksheetName, string title, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings)
        {
            return ToExcel(worksheetName, title, null, headers, datas, mappings);
        }

        public IActionResult ToExcel(string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings)
        {
            return ToExcel(worksheetName, title, subTitle, headers, datas, mappings, null!, null!, null!);
        }
        public IActionResult ToExcel(string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings, DataValueFormatOperation dataValueOperation)
        {
            return ToExcel(worksheetName, title, subTitle, headers, datas, mappings, dataValueOperation, null!, null!);
        }

        public IActionResult ToExcel(string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings, DataValueFormatOperation dataValueOperation,
          CellTypeFormatOperation cellOperation)
        {
            return ToExcel(worksheetName, title, subTitle, headers, datas, mappings, dataValueOperation, cellOperation, null!);
        }

        public IActionResult ToExcel(string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings, DataValueFormatOperation dataValueOperation,
        CellTypeFormatOperation cellOperation, XLWorksheetOperation xLWorksheetOperation)
        {
            string path = ToExcelFile(worksheetName, title, subTitle, headers, datas, mappings, dataValueOperation, cellOperation, xLWorksheetOperation);

            var stream = System.IO.File.OpenRead(path);

            return new FileStreamResult(stream, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
        }

        public void PrintExcel(string worksheetName, string title, TemplateHeader[] headers, T[] datas)
        {
            PrintExcel(worksheetName, title, null!, headers, datas, null!, null!, null!, null!);
        }

        public void PrintExcel(string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas)
        {
            PrintExcel(worksheetName, title, subTitle, headers, datas, null!, null!, null!, null!);
        }

        public void PrintExcel(string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings)
        {
            PrintExcel(worksheetName, title, subTitle, headers, datas, mappings, null!, null!, null!);
        }
        public void PrintExcel(string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings, DataValueFormatOperation dataValueOperation)
        {
            PrintExcel(worksheetName, title, subTitle, headers, datas, mappings, dataValueOperation, null!, null!);
        }

        public void PrintExcel(string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings, DataValueFormatOperation dataValueOperation,
CellTypeFormatOperation cellOperation)
        {
            PrintExcel(worksheetName, title, subTitle, headers, datas, mappings, dataValueOperation, cellOperation, null!);
        }

        public void PrintExcel(string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings, DataValueFormatOperation dataValueOperation,
CellTypeFormatOperation cellOperation, XLWorksheetOperation xLWorksheetOperation)
        {
            string fileFullname = ToExcelFile(worksheetName, title, subTitle, headers, datas, mappings, dataValueOperation, cellOperation, xLWorksheetOperation);

            Process.Start(new ProcessStartInfo
            {
                FileName = fileFullname,
                UseShellExecute = true // 使用Windows Shell执行
            });
        }

        public string ToExcelFile(string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings, DataValueFormatOperation dataValueOperation,
            CellTypeFormatOperation cellOperation, XLWorksheetOperation xLWorksheetOperation)
        {
            string tempPath = Path.GetTempPath() + $"{DateTime.Now.ToString("yyyyMMdd")}";

            if (!Directory.Exists(tempPath)) Directory.CreateDirectory(tempPath);

            string fileFullname = Path.Combine(tempPath, worksheetName.Replace(" ", "") + $"{DateTime.Now.ToString("yyyyMMddHHmmssffff")}.xlsx");

            if (service != null)
            {
                fileFullname = Path.Combine(service.Configuration["FileServiceSettings:ExcelOutputDirectory"], $"{DateTime.Now.ToString("yyyyMMdd")}", typeof(T).Name + $"{DateTime.Now.ToString("yyyyMMddHHmmssffff")}.xlsx");
            }

            using (var excel = new XLWorkbook())
            {
                if (string.IsNullOrEmpty(worksheetName)) worksheetName = "Report";

                PackWorksheet(excel, worksheetName, title, subTitle, headers, datas, mappings, dataValueOperation, cellOperation, xLWorksheetOperation);
                excel.SaveAs(fileFullname);
            }

            return fileFullname;
        }

        public IXLWorksheet PackWorksheet(XLWorkbook excel, string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings)
        {
            return PackWorksheet(excel, worksheetName, title, subTitle, headers, datas, mappings, null!, null!, null!);
        }

        public IXLWorksheet PackWorksheet(XLWorkbook excel, string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings, DataValueFormatOperation dataValueOperation)
        {
            return PackWorksheet(excel, worksheetName, title, subTitle, headers, datas, mappings, dataValueOperation, null!, null!);
        }

        public IXLWorksheet PackWorksheet(XLWorkbook excel, string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings, DataValueFormatOperation dataValueOperation,
             CellTypeFormatOperation cellOperation)
        {
            return PackWorksheet(excel, worksheetName, title, subTitle, headers, datas, mappings, dataValueOperation, cellOperation, null!);
        }

        public IXLWorksheet PackWorksheet(XLWorkbook excel, string worksheetName, string title, List<string> subTitle, TemplateHeader[] headers, T[] datas, Dictionary<string, Dictionary<string, object>> mappings, DataValueFormatOperation dataValueOperation,
            CellTypeFormatOperation cellOperation, XLWorksheetOperation xLWorksheetOperation)
        {
            List<ExportRow> ExportDatas = new List<ExportRow>();
            int rowIndex = 1;
            var worksheet = excel.Worksheets.Add(worksheetName);

            List<string> headerNames = new List<string>();
            int i = 1;
            foreach (TemplateHeader header in headers)
            {
                if (!string.IsNullOrEmpty(header.Format))
                    worksheet.Column(i).Style.NumberFormat.Format = header.Format;
                worksheet.Column(i).Width = header.DisplayName.Length * 1.5;
                if (header.Width > 0)
                    worksheet.Column(i).Width = header.Width;

                headerNames.Add(header.DisplayName);
                i++;
            }

            worksheet.Row(rowIndex).Style.Font.FontSize = 20;
            worksheet.Row(rowIndex).Height = 30;
            worksheet.Range(rowIndex, 1, rowIndex, headers.Length).Merge();
            worksheet.Cell(rowIndex++, 1).Value = title;

            //空行
            rowIndex++;
            worksheet.Row(rowIndex).Height = 20;

            if (subTitle != null && subTitle.Count > 0)
            {

                foreach (string st in subTitle)
                {
                    worksheet.Range(rowIndex, 1, rowIndex, headers.Length).Merge();
                    worksheet.Cell(rowIndex++, 1).Value = st;
                }

                //空行
                rowIndex++;
            }

            //設置列名
            var listOfList = new List<List<string>>();
            listOfList.Add(headerNames);
            worksheet.Cell(rowIndex++, 1).InsertData(listOfList);
            object[] rowData;

            int dataIndex = rowIndex;

            //Handle on empty dataset
            var listOfArr = new List<object[]>();
            if (datas != null && datas.Length > 0)
            {
                //綁定明細
                var t = datas[0].GetType();
                //里面是所有公共属性
                PropertyInfo[] pArray = t.GetProperties();

                foreach (T d in datas)
                {
                    rowData = new object[headers.Length];
                    i = 0;
                    foreach (TemplateHeader header in headers)
                    {
                        rowData[i] = null;
                        Array.ForEach<PropertyInfo>(pArray, p =>
                        {
                            if (header.BindingName == p.Name)
                            {
                                object value = p.GetValue(d);
                                if (value == null || value == DBNull.Value)
                                {
                                    rowData[i] = null;
                                }
                                else if (header.DataValueFormatName != null && dataValueOperation != null)
                                {
                                    rowData[i] = dataValueOperation(header.DataValueFormatName, value);
                                }
                                else if (value is DateTime)
                                {
                                    if (value == DBNull.Value)
                                    {
                                        rowData[i] = null;
                                    }
                                    else if (((DateTime)value).Year > 1900)
                                        rowData[i] = (DateTime)value;
                                }
                                else if (value is SqlDateTime)
                                {
                                    if (!((SqlDateTime)value).IsNull)
                                    {
                                        if (((SqlDateTime)value).Value.Year > 1900)
                                            rowData[i] = ((SqlDateTime)value).Value;
                                    }
                                    else
                                    {
                                        rowData[i] = null;
                                    }
                                }
                                else
                                {
                                    if (mappings != null && mappings.ContainsKey(header.BindingName))
                                    {
                                        if (mappings[header.BindingName].ContainsKey((string)value))
                                            rowData[i] = mappings[header.BindingName][(string)value];
                                        else
                                            rowData[i] = value;
                                    }
                                    else
                                        rowData[i] = value;
                                }
                            }
                        });

                        i++;
                    }

                    listOfArr.Add(rowData);
             
                }
            }

            worksheet.Cell(rowIndex++, 1).InsertData(listOfArr);

            if (datas != null && datas.Length > 0)
            {
                foreach (T d in datas)
                {
                    i = 1;
                    foreach (TemplateHeader header in headers)
                    {
                        if (header.CellTypeFormatName != null)
                            cellOperation(worksheet, header.CellTypeFormatName, d, dataIndex, i, datas);

                        i++;
                    }

                    dataIndex++;
                }
            }

            rowIndex = rowIndex + listOfArr.Count();

            // 3 is title line + space line + header line
            int headerRow = 3 + (subTitle == null || subTitle.Count == 0 ? 0 : subTitle.Count + 1);

            worksheet.Range(headerRow, 1, headerRow, headers.Length).Style.Fill.PatternType = XLFillPatternValues.Solid;
            worksheet.Range(headerRow, 1, headerRow, headers.Length).Style.Fill.SetBackgroundColor(XLColor.Silver);
            var range = worksheet.Range(headerRow, 1, headerRow + datas.Length, headers.Length);
        
            range.Style.Border.TopBorder = XLBorderStyleValues.Thin;
            range.Style.Border.BottomBorder = XLBorderStyleValues.Thin;
            range.Style.Border.LeftBorder = XLBorderStyleValues.Thin;
            range.Style.Border.RightBorder = XLBorderStyleValues.Thin;
            range.Style.Alignment.WrapText = true; 
            range.Style.Alignment.Vertical = XLAlignmentVerticalValues.Top;


            i = 1;
            foreach (TemplateHeader header in headers)
            {
                if (header.Width == TemplateHeader.WidthAutoFit)
                    worksheet.Column(i).AdjustToContents(headerRow, headerRow + datas.Length, 10, 100);
                i++;
            }

            if (xLWorksheetOperation != null)
                xLWorksheetOperation(worksheet, datas);

            return worksheet;
        }

        private enum ExportDisplayStyle { Header, Subtotal, Detail, Total };
        private class ExportRow
        {
            public object[] values;
            public ExportDisplayStyle displayStyle;
            public ExportRow(object[] values, ExportDisplayStyle displayStyle)
            {
                this.values = values;
                this.displayStyle = displayStyle;
            }
        }
    }
}
