using System.Text;
using System.Text.Json;
using CashLineERP.Contracts.Reporting;

namespace CashLineERP.Application.Reporting;

public sealed class CoreReportingService : ICoreReportingService
{
    private readonly ICoreReportingRepository _repository;
    private static readonly JsonSerializerOptions SnapshotJsonOptions = new(JsonSerializerDefaults.Web);

    public CoreReportingService(ICoreReportingRepository repository)
    {
        _repository = repository;
    }

    public CoreFinanceReportResponse GetCoreFinanceReport(DateOnly? dateFrom, DateOnly? dateTo)
    {
        var report = _repository.GetCoreFinanceReport(dateFrom, dateTo);
        return new CoreFinanceReportResponse(
            report.DateFrom,
            report.DateTo,
            report.BaseCurrencyCode,
            report.PostedAccountsPayableBillCount,
            report.PostedAccountsPayableBillTotal,
            report.PostedAccountsPayablePaymentCount,
            report.PostedAccountsPayablePaymentTotal,
            report.PostedAccountsReceivableInvoiceCount,
            report.PostedAccountsReceivableInvoiceTotal,
            report.PostedAccountsReceivableReceiptCount,
            report.PostedAccountsReceivableReceiptTotal,
            report.GeneralLedgerBatchCount,
            report.GeneralLedgerBatchTotal,
            report.PendingEtaInvoiceCount,
            report.PendingEtaInvoiceTotal,
            report.Rows.Select(Map).ToArray());
    }

    public CoreOperationsReportResponse GetCoreOperationsReport(DateOnly? dateFrom, DateOnly? dateTo)
    {
        var report = _repository.GetCoreOperationsReport(dateFrom, dateTo);
        return new CoreOperationsReportResponse(
            report.DateFrom,
            report.DateTo,
            report.PurchaseRequisitionCount,
            report.RequestForQuotationCount,
            report.PurchaseOrderCount,
            report.GoodsReceiptCount,
            report.PurchaseReturnCount,
            report.SalesOrderCount,
            report.DispatchCount,
            report.SalesReturnCount,
            report.InventoryAdjustmentCount,
            report.StockCountCount,
            report.WarehouseTransferCount,
            report.TotalReceivedQuantity,
            report.TotalDispatchedQuantity,
            report.TotalReturnedQuantity,
            report.TotalAdjustedAbsoluteQuantity,
            report.TotalTransferredQuantity,
            report.Rows.Select(Map).ToArray());
    }

    public IslamicFinancePipelineReportResponse GetIslamicFinancePipelineReport(DateOnly? dateFrom, DateOnly? dateTo, long? partnerLenderId = null)
    {
        var report = _repository.GetIslamicFinancePipelineReport(dateFrom, dateTo, partnerLenderId);
        return new IslamicFinancePipelineReportResponse(
            report.DateFrom,
            report.DateTo,
            report.PartnerLenderCount,
            report.ActivePartnerLenderCount,
            report.FacilityCount,
            report.ActiveFacilityCount,
            report.FinancingRequestCount,
            report.ApprovedOrAssignedRequestCount,
            report.MurabahaCaseCount,
            report.ActiveMurabahaCaseCount,
            report.TotalFacilityLimitAmount,
            report.TotalFacilityUtilizedAmount,
            report.TotalRequestedAmount,
            report.TotalMurabahaSalePriceAmount,
            report.Rows.Select(Map).ToArray());
    }

    public FinanceStakeholderSummaryReportResponse GetFinanceStakeholderSummaryReport(DateOnly? dateFrom, DateOnly? dateTo)
    {
        var report = _repository.GetFinanceStakeholderSummaryReport(dateFrom, dateTo);
        return new FinanceStakeholderSummaryReportResponse(
            report.DateFrom,
            report.DateTo,
            report.StakeholderCount,
            report.SupplierCount,
            report.CustomerCount,
            report.PartnerLenderCount,
            report.TotalOpenPayableAmount,
            report.TotalOpenReceivableAmount,
            report.TotalUnappliedAmount,
            report.TotalFacilityLimitAmount,
            report.TotalFacilityUtilizedAmount,
            report.TotalRequestedFinancingAmount,
            report.TotalMurabahaSalePriceAmount,
            report.TotalDisbursedAmount,
            report.TotalSettledAmount,
            report.Rows.Select(row => new FinanceStakeholderSummaryRowResponse(
                row.StakeholderType,
                row.StakeholderId,
                row.StakeholderCode,
                row.StakeholderName,
                row.CurrencyCode,
                row.OpenPayableAmount,
                row.OpenReceivableAmount,
                row.UnappliedAmount,
                row.FacilityLimitAmount,
                row.FacilityUtilizedAmount,
                row.RequestedFinancingAmount,
                row.MurabahaSalePriceAmount,
                row.DisbursedAmount,
                row.SettledAmount)).ToArray());
    }

    public FinanceStakeholderDetailReportResponse GetFinanceStakeholderDetailReport(string stakeholderType, long stakeholderId, string currencyCode, DateOnly? dateFrom, DateOnly? dateTo)
    {
        var report = _repository.GetFinanceStakeholderDetailReport(stakeholderType, stakeholderId, currencyCode, dateFrom, dateTo);
        return new FinanceStakeholderDetailReportResponse(
            report.DateFrom,
            report.DateTo,
            report.StakeholderType,
            report.StakeholderId,
            report.StakeholderCode,
            report.StakeholderName,
            report.CurrencyCode,
            report.OpenPayableAmount,
            report.OpenReceivableAmount,
            report.UnappliedAmount,
            report.FacilityLimitAmount,
            report.FacilityUtilizedAmount,
            report.RequestedFinancingAmount,
            report.MurabahaSalePriceAmount,
            report.DisbursedAmount,
            report.SettledAmount,
            report.Rows.Select(row => new FinanceStakeholderDetailRowResponse(
                row.ActivityDate,
                row.ActivityType,
                row.RecordId,
                row.RecordNumber,
                row.CurrencyCode,
                row.PrimaryAmount,
                row.SecondaryAmount,
                row.StatusCode,
                row.ReferenceText)).ToArray());
    }

    public FinancialStatementReportResponse GetFinancialStatementReport(string statementCode, DateOnly? dateFrom, DateOnly? dateTo, DateOnly? asOfDate)
    {
        var report = _repository.GetFinancialStatementReport(statementCode, dateFrom, dateTo, asOfDate);
        return new FinancialStatementReportResponse(
            report.StatementCode,
            report.StatementTitle,
            report.DateFrom,
            report.DateTo,
            report.AsOfDate,
            report.CurrencyCode,
            report.Columns.ToArray(),
            report.Metrics.Select(metric => new FinancialStatementMetricResponse(
                metric.MetricCode,
                metric.MetricLabel,
                metric.Amount)).ToArray(),
            report.Rows.Select(row => new FinancialStatementRowResponse(
                row.SectionCode,
                row.SectionTitle,
                row.LineCode,
                row.LineLabel,
                row.AccountingRuleId,
                row.GeneralLedgerAccountId,
                row.GeneralLedgerAccountCode,
                row.GeneralLedgerAccountName,
                row.OpeningAmount,
                row.DebitAmount,
                row.CreditAmount,
                row.NetAmount,
                row.ClosingAmount,
                row.ActivityDate,
                row.BatchNumber,
                row.JournalNumber,
                row.DocumentType,
                row.ReferenceNumber,
                row.DescriptionText)).ToArray());
    }

    public FinancialStatementExportContent ExportFinancialStatement(string statementCode, string formatCode, DateOnly? dateFrom, DateOnly? dateTo, DateOnly? asOfDate) =>
        FinancialStatementExportBuilder.Build(
            _repository.GetFinancialStatementReport(statementCode, dateFrom, dateTo, asOfDate),
            formatCode);

    public AgingReportResponse GetAccountsPayableAgingReport(DateOnly? asOfDate) =>
        Map(_repository.GetAccountsPayableAgingReport(asOfDate));

    public AgingReportResponse GetAccountsReceivableAgingReport(DateOnly? asOfDate) =>
        Map(_repository.GetAccountsReceivableAgingReport(asOfDate));

    public string ExportCoreFinanceReportCsv(DateOnly? dateFrom, DateOnly? dateTo)
    {
        var report = _repository.GetCoreFinanceReport(dateFrom, dateTo);
        var builder = new StringBuilder();
        builder.AppendLine("BusinessDate,ReportSection,DocumentType,DocumentNumber,PartyCode,CurrencyCode,BaseCurrencyCode,FxRateToBase,Amount,BaseAmount,StatusCode,ReferenceText");
        foreach (var row in report.Rows.OrderBy(entry => entry.BusinessDate).ThenBy(entry => entry.ReportSection).ThenBy(entry => entry.DocumentNumber))
        {
            builder.AppendLine(string.Join(',',
                Escape(row.BusinessDate.ToString("yyyy-MM-dd")),
                Escape(row.ReportSection),
                Escape(row.DocumentType),
                Escape(row.DocumentNumber),
                Escape(row.PartyCode),
                Escape(row.CurrencyCode),
                Escape(row.BaseCurrencyCode),
                Escape(row.FxRateToBase),
                Escape(row.Amount),
                Escape(row.BaseAmount),
                Escape(row.StatusCode),
                Escape(row.ReferenceText)));
        }

        return builder.ToString();
    }

    public string ExportCoreOperationsReportCsv(DateOnly? dateFrom, DateOnly? dateTo)
    {
        var report = _repository.GetCoreOperationsReport(dateFrom, dateTo);
        var builder = new StringBuilder();
        builder.AppendLine("ActivityDate,ActivityType,DocumentNumber,BranchId,PartyCode,StatusCode,LineCount,Quantity,Amount,ReferenceText");
        foreach (var row in report.Rows.OrderBy(entry => entry.ActivityDate).ThenBy(entry => entry.ActivityType).ThenBy(entry => entry.DocumentNumber))
        {
            builder.AppendLine(string.Join(',',
                Escape(row.ActivityDate.ToString("yyyy-MM-dd")),
                Escape(row.ActivityType),
                Escape(row.DocumentNumber),
                Escape(row.BranchId),
                Escape(row.PartyCode),
                Escape(row.StatusCode),
                Escape(row.LineCount),
                Escape(row.Quantity),
                Escape(row.Amount),
                Escape(row.ReferenceText)));
        }

        return builder.ToString();
    }

    public string ExportIslamicFinancePipelineReportCsv(DateOnly? dateFrom, DateOnly? dateTo, long? partnerLenderId = null)
    {
        var report = _repository.GetIslamicFinancePipelineReport(dateFrom, dateTo, partnerLenderId);
        var builder = new StringBuilder();
        builder.AppendLine("ActivityDate,StageCode,RecordNumber,RecordId,PartnerLenderCode,PartnerLenderName,CustomerCode,CustomerName,ProductCode,ProductName,CurrencyCode,StatusCode,PrimaryAmount,SecondaryAmount,ReferenceText");
        foreach (var row in report.Rows.OrderBy(entry => entry.ActivityDate).ThenBy(entry => entry.StageCode).ThenBy(entry => entry.RecordNumber))
        {
            builder.AppendLine(string.Join(',',
                Escape(row.ActivityDate.ToString("yyyy-MM-dd")),
                Escape(row.StageCode),
                Escape(row.RecordNumber),
                Escape(row.RecordId),
                Escape(row.PartnerLenderCode),
                Escape(row.PartnerLenderName),
                Escape(row.CustomerCode),
                Escape(row.CustomerName),
                Escape(row.ProductCode),
                Escape(row.ProductName),
                Escape(row.CurrencyCode),
                Escape(row.StatusCode),
                Escape(row.PrimaryAmount),
                Escape(row.SecondaryAmount),
                Escape(row.ReferenceText)));
        }

        return builder.ToString();
    }

    public string ExportFinanceStakeholderSummaryReportCsv(DateOnly? dateFrom, DateOnly? dateTo)
    {
        var report = _repository.GetFinanceStakeholderSummaryReport(dateFrom, dateTo);
        var builder = new StringBuilder();
        builder.AppendLine("StakeholderType,StakeholderId,StakeholderCode,StakeholderName,CurrencyCode,OpenPayableAmount,OpenReceivableAmount,UnappliedAmount,FacilityLimitAmount,FacilityUtilizedAmount,RequestedFinancingAmount,MurabahaSalePriceAmount,DisbursedAmount,SettledAmount");
        foreach (var row in report.Rows.OrderBy(entry => entry.StakeholderType).ThenBy(entry => entry.StakeholderCode).ThenBy(entry => entry.CurrencyCode))
        {
            builder.AppendLine(string.Join(',',
                Escape(row.StakeholderType),
                Escape(row.StakeholderId),
                Escape(row.StakeholderCode),
                Escape(row.StakeholderName),
                Escape(row.CurrencyCode),
                Escape(row.OpenPayableAmount),
                Escape(row.OpenReceivableAmount),
                Escape(row.UnappliedAmount),
                Escape(row.FacilityLimitAmount),
                Escape(row.FacilityUtilizedAmount),
                Escape(row.RequestedFinancingAmount),
                Escape(row.MurabahaSalePriceAmount),
                Escape(row.DisbursedAmount),
                Escape(row.SettledAmount)));
        }

        return builder.ToString();
    }

    public string ExportFinanceStakeholderDetailReportCsv(string stakeholderType, long stakeholderId, string currencyCode, DateOnly? dateFrom, DateOnly? dateTo)
    {
        var report = _repository.GetFinanceStakeholderDetailReport(stakeholderType, stakeholderId, currencyCode, dateFrom, dateTo);
        var builder = new StringBuilder();
        builder.AppendLine("ActivityDate,ActivityType,RecordId,RecordNumber,CurrencyCode,PrimaryAmount,SecondaryAmount,StatusCode,ReferenceText");
        foreach (var row in report.Rows.OrderBy(entry => entry.ActivityDate).ThenBy(entry => entry.ActivityType).ThenBy(entry => entry.RecordNumber))
        {
            builder.AppendLine(string.Join(',',
                Escape(row.ActivityDate.ToString("yyyy-MM-dd")),
                Escape(row.ActivityType),
                Escape(row.RecordId),
                Escape(row.RecordNumber),
                Escape(row.CurrencyCode),
                Escape(row.PrimaryAmount),
                Escape(row.SecondaryAmount),
                Escape(row.StatusCode),
                Escape(row.ReferenceText)));
        }

        return builder.ToString();
    }

    public string ExportAccountsPayableAgingReportCsv(DateOnly? asOfDate) =>
        ExportAgingCsv(_repository.GetAccountsPayableAgingReport(asOfDate));

    public string ExportAccountsReceivableAgingReportCsv(DateOnly? asOfDate) =>
        ExportAgingCsv(_repository.GetAccountsReceivableAgingReport(asOfDate));

    public IReadOnlyCollection<ReportingSnapshotSummaryResponse> GetReportingSnapshots(string? reportCode = null) =>
        _repository.GetReportingSnapshots(reportCode)
            .Select(Map)
            .ToArray();

    public ReportingSnapshotResponse? GetReportingSnapshot(long reportingSnapshotId)
    {
        var snapshot = _repository.GetReportingSnapshot(reportingSnapshotId);
        return snapshot is null ? null : Map(snapshot);
    }

    public ReportingSnapshotResponse GenerateReportingSnapshot(long actorUserId, CreateReportingSnapshotRequest request)
    {
        var (normalizedReportCode, reportTitle) = ReportingSnapshotCatalog.Normalize(request.ReportCode);
        ReportingSnapshotCatalog.ValidateWindow(normalizedReportCode, request.DateFrom, request.DateTo, request.AsOfDate);

        var generated = normalizedReportCode switch
        {
            "CORE_FINANCE" => BuildCoreFinanceSnapshot(actorUserId, reportTitle, request),
            "CORE_OPERATIONS" => BuildCoreOperationsSnapshot(actorUserId, reportTitle, request),
            "AP_AGING" => BuildAccountsPayableAgingSnapshot(actorUserId, reportTitle, request),
            "AR_AGING" => BuildAccountsReceivableAgingSnapshot(actorUserId, reportTitle, request),
            _ => throw new InvalidOperationException($"Unsupported reporting snapshot report code '{request.ReportCode}'.")
        };

        return Map(generated);
    }

    private static CoreFinanceReportRowResponse Map(CoreFinanceReportRowView row) =>
        new(
            row.BusinessDate,
            row.ReportSection,
            row.DocumentType,
            row.DocumentNumber,
            row.PartyCode,
            row.CurrencyCode,
            row.BaseCurrencyCode,
            row.FxRateToBase,
            row.BaseAmount,
            row.StatusCode,
            row.Amount,
            row.ReferenceText);

    private static CoreOperationsReportRowResponse Map(CoreOperationsReportRowView row) =>
        new(
            row.ActivityDate,
            row.ActivityType,
            row.DocumentNumber,
            row.BranchId,
            row.PartyCode,
            row.StatusCode,
            row.LineCount,
            row.Quantity,
            row.Amount,
            row.ReferenceText);

    private static IslamicFinancePipelineReportRowResponse Map(IslamicFinancePipelineReportRowView row) =>
        new(
            row.ActivityDate,
            row.StageCode,
            row.RecordNumber,
            row.RecordId,
            row.PartnerLenderId,
            row.PartnerLenderCode,
            row.PartnerLenderName,
            row.CustomerPartyId,
            row.CustomerCode,
            row.CustomerName,
            row.ProductProfileId,
            row.ProductCode,
            row.ProductName,
            row.CurrencyCode,
            row.StatusCode,
            row.PrimaryAmount,
            row.SecondaryAmount,
            row.ReferenceText);

    private static AgingReportResponse Map(AgingReportView report) =>
        new(
            report.LedgerSideCode,
            report.AsOfDate,
            report.DocumentCount,
            report.PartyCount,
            report.TotalOutstandingAmount,
            report.CurrentAmount,
            report.Bucket1To30Amount,
            report.Bucket31To60Amount,
            report.Bucket61To90Amount,
            report.Bucket90PlusAmount,
            report.Rows.Select(row => new AgingReportRowResponse(
                row.LedgerSideCode,
                row.DocumentType,
                row.DocumentId,
                row.DocumentNumber,
                row.PartyId,
                row.PartyCode,
                row.PartyName,
                row.DocumentDate,
                row.DueDate,
                row.CurrencyCode,
                row.OriginalAmount,
                row.OutstandingAmount,
                row.DaysPastDue,
                row.AgingBucketCode)).ToArray());

    private static string ExportAgingCsv(AgingReportView report)
    {
        var builder = new StringBuilder();
        builder.AppendLine("LedgerSide,DocumentType,DocumentNumber,PartyCode,PartyName,DocumentDate,DueDate,CurrencyCode,OriginalAmount,OutstandingAmount,DaysPastDue,AgingBucketCode");
        foreach (var row in report.Rows.OrderBy(entry => entry.PartyCode).ThenBy(entry => entry.DueDate).ThenBy(entry => entry.DocumentNumber))
        {
            builder.AppendLine(string.Join(',',
                Escape(row.LedgerSideCode),
                Escape(row.DocumentType),
                Escape(row.DocumentNumber),
                Escape(row.PartyCode),
                Escape(row.PartyName),
                Escape(row.DocumentDate.ToString("yyyy-MM-dd")),
                Escape(row.DueDate?.ToString("yyyy-MM-dd")),
                Escape(row.CurrencyCode),
                Escape(row.OriginalAmount),
                Escape(row.OutstandingAmount),
                Escape(row.DaysPastDue),
                Escape(row.AgingBucketCode)));
        }

        return builder.ToString();
    }

    private static string Escape(object? value)
    {
        var text = value?.ToString() ?? string.Empty;
        if (!text.Contains(',') && !text.Contains('"') && !text.Contains('\n'))
        {
            return text;
        }

        return $"\"{text.Replace("\"", "\"\"")}\"";
    }

    private ReportingSnapshotDetailView BuildCoreFinanceSnapshot(long actorUserId, string reportTitle, CreateReportingSnapshotRequest request)
    {
        var response = GetCoreFinanceReport(request.DateFrom, request.DateTo);
        return _repository.CreateReportingSnapshot(
            actorUserId,
            new CreateReportingSnapshotCommand(
                "CORE_FINANCE",
                reportTitle,
                response.DateFrom,
                response.DateTo,
                null,
                response.Rows.Count,
                JsonSerializer.Serialize(response, SnapshotJsonOptions),
                ExportCoreFinanceReportCsv(request.DateFrom, request.DateTo),
                request.Notes));
    }

    private ReportingSnapshotDetailView BuildCoreOperationsSnapshot(long actorUserId, string reportTitle, CreateReportingSnapshotRequest request)
    {
        var response = GetCoreOperationsReport(request.DateFrom, request.DateTo);
        return _repository.CreateReportingSnapshot(
            actorUserId,
            new CreateReportingSnapshotCommand(
                "CORE_OPERATIONS",
                reportTitle,
                response.DateFrom,
                response.DateTo,
                null,
                response.Rows.Count,
                JsonSerializer.Serialize(response, SnapshotJsonOptions),
                ExportCoreOperationsReportCsv(request.DateFrom, request.DateTo),
                request.Notes));
    }

    private ReportingSnapshotDetailView BuildAccountsPayableAgingSnapshot(long actorUserId, string reportTitle, CreateReportingSnapshotRequest request)
    {
        var response = GetAccountsPayableAgingReport(request.AsOfDate);
        return _repository.CreateReportingSnapshot(
            actorUserId,
            new CreateReportingSnapshotCommand(
                "AP_AGING",
                reportTitle,
                null,
                null,
                response.AsOfDate,
                response.Rows.Count,
                JsonSerializer.Serialize(response, SnapshotJsonOptions),
                ExportAccountsPayableAgingReportCsv(request.AsOfDate),
                request.Notes));
    }

    private ReportingSnapshotDetailView BuildAccountsReceivableAgingSnapshot(long actorUserId, string reportTitle, CreateReportingSnapshotRequest request)
    {
        var response = GetAccountsReceivableAgingReport(request.AsOfDate);
        return _repository.CreateReportingSnapshot(
            actorUserId,
            new CreateReportingSnapshotCommand(
                "AR_AGING",
                reportTitle,
                null,
                null,
                response.AsOfDate,
                response.Rows.Count,
                JsonSerializer.Serialize(response, SnapshotJsonOptions),
                ExportAccountsReceivableAgingReportCsv(request.AsOfDate),
                request.Notes));
    }

    private static ReportingSnapshotSummaryResponse Map(ReportingSnapshotSummaryView snapshot) =>
        new(
            snapshot.ReportingSnapshotId,
            snapshot.SnapshotNumber,
            snapshot.ReportCode,
            snapshot.ReportTitle,
            snapshot.DateFrom,
            snapshot.DateTo,
            snapshot.AsOfDate,
            snapshot.RowCount,
            snapshot.GeneratedAtUtc,
            snapshot.GeneratedByUserId,
            snapshot.Notes);

    private static ReportingSnapshotResponse Map(ReportingSnapshotDetailView snapshot) =>
        new(
            snapshot.ReportingSnapshotId,
            snapshot.SnapshotNumber,
            snapshot.ReportCode,
            snapshot.ReportTitle,
            snapshot.DateFrom,
            snapshot.DateTo,
            snapshot.AsOfDate,
            snapshot.RowCount,
            snapshot.JsonPayloadText,
            snapshot.CsvPayloadText,
            snapshot.GeneratedAtUtc,
            snapshot.GeneratedByUserId,
            snapshot.Notes);
}
