using ClosedXML.Excel;
using WorkTracker.Domain;

namespace WorkTracker.Services.Exports;

public sealed class MonthlyTimesheetExcelExporter : IMonthlyTimesheetExcelExporter
{
    private const int DayHeaderRow = 1;
    private const int DayStartColumn = 3;
    private const int TemplateDayCount = 30;

    private static readonly IReadOnlyDictionary<string, int> TimesheetRowMap = new Dictionary<string, int>(StringComparer.Ordinal)
    {
        ["office"] = 2,
        ["home"] = 4,
        ["overtime"] = 5,
        ["weekend"] = 6,
        ["night"] = 7,
        ["vacation"] = 8,
        ["permit"] = 9,
        ["compensatory-rest"] = 10,
        ["sick"] = 11,
        ["holiday"] = 12
    };

    public byte[] Export(MonthlyTimesheetModel timesheet, Stream templateStream)
    {
        ArgumentNullException.ThrowIfNull(timesheet);
        ArgumentNullException.ThrowIfNull(templateStream);

        if (templateStream.CanSeek)
        {
            templateStream.Position = 0;
        }

        using var workbook = new XLWorkbook(templateStream);
        workbook.DefinedNames.DeleteAll();
        var worksheet = workbook.Worksheet(1);
        var dayCount = timesheet.Days.Count;

        AdjustDayColumns(worksheet, dayCount);
        ApplyDayHeaders(worksheet, timesheet.Days);
        ApplyRowValues(worksheet, timesheet.Rows, dayCount);
        ApplyTotalFormulas(worksheet, dayCount);

        using var output = new MemoryStream();
        workbook.SaveAs(output);
        return output.ToArray();
    }

    private static void AdjustDayColumns(IXLWorksheet worksheet, int dayCount)
    {
        var totalColumn = DayStartColumn + TemplateDayCount;
        var delta = dayCount - TemplateDayCount;

        if (delta > 0)
        {
            worksheet.Column(totalColumn).InsertColumnsBefore(delta);
        }
        else if (delta < 0)
        {
            worksheet.Columns(DayStartColumn + dayCount, DayStartColumn + TemplateDayCount - 1).Delete();
        }
    }

    private static void ApplyDayHeaders(IXLWorksheet worksheet, IReadOnlyList<MonthlyTimesheetDayModel> days)
    {
        var lastDayColumn = DayStartColumn + days.Count - 1;

        for (var index = 0; index < days.Count; index++)
        {
            var day = days[index];
            var column = DayStartColumn + index;

            var headerCell = worksheet.Cell(DayHeaderRow, column);
            headerCell.Value = day.Date.Day;
            ApplyDayStyle(worksheet, headerCell, day, column, lastDayColumn, 1);

            foreach (var rowNumber in TimesheetRowMap.Values)
            {
                ApplyDayStyle(worksheet, worksheet.Cell(rowNumber, column), day, column, lastDayColumn, rowNumber);
            }
        }
    }

    private static void ApplyRowValues(IXLWorksheet worksheet, IReadOnlyList<MonthlyTimesheetRowModel> rows, int dayCount)
    {
        foreach (var row in rows)
        {
            if (!TimesheetRowMap.TryGetValue(row.Key, out var worksheetRow))
            {
                continue;
            }

            for (var dayIndex = 0; dayIndex < dayCount; dayIndex++)
            {
                var cell = worksheet.Cell(worksheetRow, DayStartColumn + dayIndex);
                var value = dayIndex < row.DailyValues.Count ? row.DailyValues[dayIndex] : null;
                if (value.HasValue && value.Value > 0m)
                {
                    cell.Value = value.Value;
                }
                else
                {
                    cell.Clear(XLClearOptions.Contents);
                }
            }
        }
    }

    private static void ApplyTotalFormulas(IXLWorksheet worksheet, int dayCount)
    {
        var lastDayColumn = DayStartColumn + dayCount - 1;
        var totalColumn = lastDayColumn + 1;
        var firstDayColumnLetter = XLHelper.GetColumnLetterFromNumber(DayStartColumn);
        var lastDayColumnLetter = XLHelper.GetColumnLetterFromNumber(lastDayColumn);

        foreach (var rowNumber in TimesheetRowMap.Values)
        {
            worksheet.Cell(rowNumber, totalColumn).FormulaA1 = $"SUM({firstDayColumnLetter}{rowNumber}:{lastDayColumnLetter}{rowNumber})";
        }
    }

    private static void ApplyDayStyle(IXLWorksheet worksheet, IXLCell targetCell, MonthlyTimesheetDayModel day, int column, int lastDayColumn, int rowNumber)
    {
        var baseCell = worksheet.Cell(GetBaseStyleRowAddress(rowNumber), GetBaseStyleColumn(column, lastDayColumn));
        var baseStyle = baseCell.Style;

        if (!ShouldHighlight(day))
        {
            targetCell.Style = baseStyle;
            return;
        }

        var sampleColumn = GetHighlightSampleColumn(day, rowNumber);
        var sampleCell = worksheet.Cell(GetSampleStyleRow(rowNumber), sampleColumn);
        var sampleStyle = sampleCell.Style;

        targetCell.Style = sampleStyle;
        targetCell.Style.Border.LeftBorder = baseStyle.Border.LeftBorder;
        targetCell.Style.Border.LeftBorderColor = baseStyle.Border.LeftBorderColor;
        targetCell.Style.Border.RightBorder = baseStyle.Border.RightBorder;
        targetCell.Style.Border.RightBorderColor = baseStyle.Border.RightBorderColor;
        targetCell.Style.Border.TopBorder = baseStyle.Border.TopBorder;
        targetCell.Style.Border.TopBorderColor = baseStyle.Border.TopBorderColor;
        targetCell.Style.Border.BottomBorder = baseStyle.Border.BottomBorder;
        targetCell.Style.Border.BottomBorderColor = baseStyle.Border.BottomBorderColor;
    }

    private static bool ShouldHighlight(MonthlyTimesheetDayModel day)
    {
        return day.IsWeekend || day.IsHoliday;
    }

    private static int GetBaseStyleRowAddress(int rowNumber)
    {
        return rowNumber switch
        {
            1 => 1,
            2 => 2,
            4 or 5 or 6 or 7 or 8 or 9 or 10 or 11 => 4,
            12 => 12,
            _ => rowNumber
        };
    }

    private static int GetSampleStyleRow(int rowNumber)
    {
        return rowNumber switch
        {
            1 => 1,
            2 => 2,
            4 or 5 or 6 or 7 or 8 or 9 or 10 or 11 => 4,
            12 => 12,
            _ => rowNumber
        };
    }

    private static int GetBaseStyleColumn(int column, int lastDayColumn)
    {
        if (column == DayStartColumn)
        {
            return DayStartColumn;
        }

        if (column == lastDayColumn)
        {
            return lastDayColumn == DayStartColumn + TemplateDayCount - 1
                ? DayStartColumn + TemplateDayCount - 1
                : DayStartColumn + 1;
        }

        return DayStartColumn + 1;
    }

    private static int GetHighlightSampleColumn(MonthlyTimesheetDayModel day, int rowNumber)
    {
        if (rowNumber == 1)
        {
            if (day.IsHoliday || day.Date.DayOfWeek == DayOfWeek.Sunday)
            {
                return 7;
            }

            if (day.Date.DayOfWeek == DayOfWeek.Saturday)
            {
                return 6;
            }

            return 7;
        }

        if (rowNumber == 2)
        {
            if (day.IsHoliday || day.Date.DayOfWeek == DayOfWeek.Sunday)
            {
                return 7;
            }

            if (day.Date.DayOfWeek == DayOfWeek.Saturday)
            {
                return 6;
            }

            return 7;
        }

        return 6;
    }
}