using System.Globalization;
using System.IO.Compression;
using MediatR;
using TakeoutSaaS.Application.App.Finance.Reports.Dto;
using TakeoutSaaS.Application.App.Finance.Reports.Queries;
using TakeoutSaaS.Domain.Finance.Repositories;
using TakeoutSaaS.Domain.Finance.Services;
using TakeoutSaaS.Shared.Abstractions.Tenancy;

namespace TakeoutSaaS.Application.App.Finance.Reports.Handlers;

/// <summary>
/// 经营报表批量导出处理器（ZIP：PDF + Excel）。
/// </summary>
public sealed class ExportFinanceBusinessReportBatchQueryHandler(
    IFinanceBusinessReportRepository financeBusinessReportRepository,
    IFinanceBusinessReportExportService financeBusinessReportExportService,
    ITenantProvider tenantProvider)
    : IRequestHandler<ExportFinanceBusinessReportBatchQuery, FinanceBusinessReportExportDto>
{
    /// <inheritdoc />
    public async Task<FinanceBusinessReportExportDto> Handle(
        ExportFinanceBusinessReportBatchQuery request,
        CancellationToken cancellationToken)
    {
        // 1. 读取租户上下文并归一化分页参数。
        var tenantId = tenantProvider.GetCurrentTenantId();
        var normalizedPage = Math.Max(1, request.Page);
        var normalizedPageSize = Math.Clamp(request.PageSize, 1, 200);

        // 2. 确保成本配置并补齐快照。
        await financeBusinessReportRepository.EnsureDefaultCostProfilesAsync(
            tenantId,
            request.StoreId,
            cancellationToken);
        await financeBusinessReportRepository.QueueSnapshotsForPageAsync(
            tenantId,
            request.StoreId,
            request.PeriodType,
            normalizedPage,
            normalizedPageSize,
            cancellationToken);

        // 3. 查询导出明细集合（允许实时补算）。
        var details = await financeBusinessReportRepository.ListBatchDetailsAsync(
            tenantId,
            request.StoreId,
            request.PeriodType,
            normalizedPage,
            normalizedPageSize,
            allowRealtimeBuild: true,
            cancellationToken);

        // 4. 生成批量 PDF/Excel 并打包 ZIP。
        var periodCode = FinanceBusinessReportMapping.ToPeriodTypeCode(request.PeriodType);
        var zipBytes = await CreateZipAsync(
            details,
            periodCode,
            financeBusinessReportExportService,
            cancellationToken);

        return new FinanceBusinessReportExportDto
        {
            FileName = string.Create(
                CultureInfo.InvariantCulture,
                $"business-report-batch-{request.StoreId}-{periodCode}-{DateTime.UtcNow:yyyyMMddHHmmss}.zip"),
            FileContentBase64 = Convert.ToBase64String(zipBytes),
            TotalCount = details.Count
        };
    }

    private static async Task<byte[]> CreateZipAsync(
        IReadOnlyList<Domain.Finance.Models.FinanceBusinessReportDetailSnapshot> details,
        string periodCode,
        IFinanceBusinessReportExportService exportService,
        CancellationToken cancellationToken)
    {
        if (details.Count == 0)
        {
            using var emptyStream = new MemoryStream();
            using (var emptyArchive = new ZipArchive(emptyStream, ZipArchiveMode.Create, true))
            {
                var entry = emptyArchive.CreateEntry("README.txt");
                await using var writer = new StreamWriter(entry.Open());
                await writer.WriteAsync("No business report data in current selection.");
            }

            return emptyStream.ToArray();
        }

        var pdfBytes = await exportService.ExportBatchPdfAsync(details, cancellationToken);
        var excelBytes = await exportService.ExportBatchExcelAsync(details, cancellationToken);

        using var stream = new MemoryStream();
        using (var archive = new ZipArchive(stream, ZipArchiveMode.Create, true))
        {
            var pdfEntry = archive.CreateEntry($"business-report-{periodCode}.pdf");
            await using (var pdfEntryStream = pdfEntry.Open())
            {
                await pdfEntryStream.WriteAsync(pdfBytes, cancellationToken);
            }

            var excelEntry = archive.CreateEntry($"business-report-{periodCode}.xlsx");
            await using (var excelEntryStream = excelEntry.Open())
            {
                await excelEntryStream.WriteAsync(excelBytes, cancellationToken);
            }
        }

        return stream.ToArray();
    }
}
