using ClosedXML.Excel;
using Xunit;

namespace WorkTracker.Tests;

internal static class WorkbookAssert
{
    public static void Equivalent(string expectedPath, byte[] actualContent)
    {
        using var expectedWorkbook = new XLWorkbook(expectedPath);
        using var actualStream = new MemoryStream(actualContent);
        using var actualWorkbook = new XLWorkbook(actualStream);

        var expectedWorksheet = expectedWorkbook.Worksheet(1);
        var actualWorksheet = actualWorkbook.Worksheet(1);

        Assert.Equal(expectedWorksheet.Name, actualWorksheet.Name);
        Assert.Equal(expectedWorksheet.RangeUsed()?.RangeAddress.ToString(), actualWorksheet.RangeUsed()?.RangeAddress.ToString());
        Assert.Equal(expectedWorksheet.MergedRanges.Select(range => range.RangeAddress.ToString()), actualWorksheet.MergedRanges.Select(range => range.RangeAddress.ToString()));

        var usedRange = expectedWorksheet.RangeUsed() ?? throw new InvalidOperationException("Expected workbook must have a used range.");
        foreach (var column in Enumerable.Range(usedRange.RangeAddress.FirstAddress.ColumnNumber, usedRange.ColumnCount()))
        {
            Assert.Equal(Math.Round(expectedWorksheet.Column(column).Width, 5), Math.Round(actualWorksheet.Column(column).Width, 5));
        }

        foreach (var cell in usedRange.Cells())
        {
            var actualCell = actualWorksheet.Cell(cell.Address.RowNumber, cell.Address.ColumnNumber);
            Assert.Equal(GetCellValue(cell), GetCellValue(actualCell));
            Assert.Equal(cell.FormulaA1, actualCell.FormulaA1);

            if (string.IsNullOrEmpty(cell.FormulaA1))
            {
                Assert.Equal(cell.DataType, actualCell.DataType);
            }

            var expectedStyle = DescribeStyle(cell.Style);
            var actualStyle = DescribeStyle(actualCell.Style);
            Assert.True(expectedStyle == actualStyle, $"Style mismatch at {cell.Address}: expected '{expectedStyle}' actual '{actualStyle}'");
        }
    }

    private static string GetCellValue(IXLCell cell)
    {
        if (!string.IsNullOrEmpty(cell.FormulaA1))
        {
            return string.Empty;
        }

        return cell.Value.ToString();
    }

    private static string DescribeStyle(IXLStyle style)
    {
        return string.Join(
            "|",
            style.NumberFormat.NumberFormatId,
            style.NumberFormat.Format,
            style.Fill.PatternType,
            DescribeColor(style.Fill.BackgroundColor),
            DescribeColor(style.Fill.PatternColor),
            style.Font.FontName,
            style.Font.FontSize,
            style.Font.Bold,
            style.Alignment.Horizontal,
            style.Alignment.Vertical,
            style.Alignment.WrapText,
            style.Border.LeftBorder,
            DescribeColor(style.Border.LeftBorderColor),
            style.Border.RightBorder,
            DescribeColor(style.Border.RightBorderColor),
            style.Border.TopBorder,
            DescribeColor(style.Border.TopBorderColor),
            style.Border.BottomBorder,
            DescribeColor(style.Border.BottomBorderColor),
            style.Protection.Locked);
    }

    private static string DescribeColor(XLColor color)
    {
        return color.ColorType switch
        {
            XLColorType.Color => $"rgb:{color.Color.ToArgb()}",
            XLColorType.Indexed => $"indexed:{color.Indexed}",
                XLColorType.Theme => $"theme:{color.ThemeColor}:{Math.Round(color.ThemeTint, 12)}",
            _ => color.ColorType.ToString()
        };
    }
}
