using System.Globalization;
using System.Text;
using ClosedXML.Excel;
using CsvHelper;
using CsvHelper.Configuration;
using Microsoft.AspNetCore.Mvc;

namespace foodmarket.Api.Controllers.Reports;

/// <summary>Хелперы экспорта табличных данных отчётов в CSV и XLSX.
///
/// CSV: разделитель «;» (Excel-RU открывает корректно без import wizard),
/// BOM UTF-8 (тот же повод), `CultureInfo.InvariantCulture` для чисел и дат
/// (так в файл попадает «1234.56», а не «1 234,56» — Excel сам отформатирует
/// по локали при чтении). Заголовки берутся из имён свойств — это compromise,
/// но контроллеры могут передать <c>columnNames</c> для русификации.
///
/// XLSX: ClosedXML без шрифтов/стилей сверх минимума — отчёты предполагается
/// открывать в Excel и форматировать там. Числовые ячейки реально числа
/// (а не строки), даты — DateTime; auto-fit колонок включён.</summary>
public static class ReportExport
{
    public static IActionResult Csv<T>(IEnumerable<T> rows, string fileName,
        IReadOnlyList<string>? columnNames = null)
    {
        using var ms = new MemoryStream();
        var preamble = Encoding.UTF8.GetPreamble();
        ms.Write(preamble, 0, preamble.Length);
        using (var writer = new StreamWriter(ms, new UTF8Encoding(false), leaveOpen: true))
        using (var csv = new CsvWriter(writer, new CsvConfiguration(CultureInfo.InvariantCulture)
        {
            Delimiter = ";",
            // Если контроллер передал русские заголовки — мы их пишем сами и просим
            // CsvHelper не генерить заголовок повторно. Иначе — пусть пишет
            // заголовок из имён свойств.
            HasHeaderRecord = columnNames is null || columnNames.Count == 0,
        }))
        {
            if (columnNames is not null && columnNames.Count > 0)
            {
                foreach (var h in columnNames) csv.WriteField(h);
                csv.NextRecord();
            }
            csv.WriteRecords(rows);
        }
        return new FileContentResult(ms.ToArray(), "text/csv; charset=utf-8")
        {
            FileDownloadName = fileName.EndsWith(".csv") ? fileName : fileName + ".csv",
        };
    }

    /// <summary>Простой экспорт «таблица в XLSX». В строке reflection берёт
    /// публичные свойства rows[0], ставит заголовки и значения. Для пустого
    /// источника возвращает пустой лист с заголовками из дженерик-типа.</summary>
    public static IActionResult Xlsx<T>(IEnumerable<T> rows, string fileName,
        string sheetName = "Report",
        IReadOnlyList<string>? columnNames = null)
    {
        using var wb = new XLWorkbook();
        var ws = wb.AddWorksheet(sheetName.Length > 31 ? sheetName[..31] : sheetName);
        var props = typeof(T).GetProperties();
        for (int i = 0; i < props.Length; i++)
        {
            ws.Cell(1, i + 1).Value = (columnNames is not null && i < columnNames.Count)
                ? columnNames[i]
                : props[i].Name;
            ws.Cell(1, i + 1).Style.Font.Bold = true;
        }
        var r = 2;
        foreach (var row in rows)
        {
            for (int i = 0; i < props.Length; i++)
            {
                var v = props[i].GetValue(row);
                var c = ws.Cell(r, i + 1);
                switch (v)
                {
                    case null: c.Value = string.Empty; break;
                    case DateTime dt: c.Value = dt; c.Style.NumberFormat.Format = "yyyy-mm-dd hh:mm"; break;
                    case decimal dec: c.Value = dec; break;
                    case double dbl: c.Value = dbl; break;
                    case int ii: c.Value = ii; break;
                    case long ll: c.Value = ll; break;
                    case bool bb: c.Value = bb; break;
                    case Guid g: c.Value = g.ToString(); break;
                    default: c.Value = v.ToString(); break;
                }
            }
            r++;
        }
        ws.Columns().AdjustToContents();
        using var ms = new MemoryStream();
        wb.SaveAs(ms);
        return new FileContentResult(ms.ToArray(),
            "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
        {
            FileDownloadName = fileName.EndsWith(".xlsx") ? fileName : fileName + ".xlsx",
        };
    }
}

/// <summary>Универсальный диапазон дат отчёта. Обе границы — UTC.
/// Пустые значения подменяются дефолтами в контроллере (обычно last 30 days).</summary>
public sealed record DateRange(DateTime From, DateTime To)
{
    public static DateRange LastDays(int days)
    {
        var to = DateTime.UtcNow;
        return new DateRange(to.AddDays(-days), to);
    }
}
