using CashLineERP.Contracts.Finance;

namespace CashLineERP.Application.Finance;

public sealed class ReconciliationService : IReconciliationService
{
    private readonly IReconciliationRepository _repository;

    public ReconciliationService(IReconciliationRepository repository)
    {
        _repository = repository;
    }

    public IReadOnlyCollection<BankStatementResponse> GetBankStatements() =>
        _repository.GetBankStatements().Select(Map).ToArray();

    public BankStatementResponse? GetBankStatement(long bankStatementId)
    {
        var statement = _repository.GetBankStatement(bankStatementId);
        return statement is null ? null : Map(statement);
    }

    public BankStatementResponse CreateBankStatement(CreateBankStatementRequest request)
    {
        var statement = _repository.CreateBankStatement(new CreateBankStatementCommand(
            request.BankAccountCode,
            request.StatementDate,
            request.CurrencyCode,
            request.OpeningBalanceAmount,
            request.ClosingBalanceAmount,
            request.Lines.Select(line => new CreateBankStatementLineCommand(
                line.TransactionDate,
                line.ExternalReference,
                line.DescriptionText,
                line.DebitAmount,
                line.CreditAmount,
                line.RunningBalanceAmount)).ToArray()));

        return Map(statement);
    }

    public IReadOnlyCollection<ReconciliationRunResponse> GetRuns() =>
        _repository.GetRuns().Select(Map).ToArray();

    public ReconciliationRunResponse? GetRun(long receiptReconciliationRunId)
    {
        var run = _repository.GetRun(receiptReconciliationRunId);
        return run is null ? null : Map(run);
    }

    public ReconciliationRunResponse RunReceiptReconciliation(RunReceiptReconciliationRequest request) =>
        Map(_repository.RunReceiptReconciliation(new RunReceiptReconciliationCommand(
            request.AccountingPeriodId,
            request.BankStatementId,
            request.ActorUserId)));

    public ReconciliationRunResponse RunSettlementReconciliation(RunReceiptReconciliationRequest request) =>
        Map(_repository.RunSettlementReconciliation(new RunReceiptReconciliationCommand(
            request.AccountingPeriodId,
            request.BankStatementId,
            request.ActorUserId)));

    public IReadOnlyCollection<ReconciliationExceptionQueueItemResponse> GetExceptionQueue(string? matchStatusCode = null) =>
        _repository.GetExceptionQueue(matchStatusCode).Select(Map).ToArray();

    public IReadOnlyCollection<ReconciliationHistoryEntryResponse> GetHistory(long? receiptReconciliationRunId = null, long? bankStatementId = null, string? resolutionCode = null, string? matchedDocumentType = null) =>
        _repository.GetHistory(receiptReconciliationRunId, bankStatementId, resolutionCode, matchedDocumentType).Select(Map).ToArray();

    public ReconciliationAuditResponse? GetAudit(long receiptReconciliationItemId)
    {
        var audit = _repository.GetAudit(receiptReconciliationItemId);
        return audit is null ? null : Map(audit);
    }

    public BulkResolveReconciliationItemsResponse BulkResolve(BulkResolveReconciliationItemsRequest request)
    {
        if (request.Items.Count == 0)
        {
            throw new InvalidOperationException("Bulk reconciliation resolution requires at least one item.");
        }

        var results = request.Items
            .Select(item =>
            {
                var normalizedActionCode = item.ResolutionActionCode.Trim().ToUpperInvariant();

                try
                {
                    var resolvedItem = normalizedActionCode switch
                    {
                        "MANUAL_MATCH" => ManualMatch(
                            item.ReceiptReconciliationItemId,
                            new ManualMatchReconciliationItemRequest(
                                request.ActorUserId,
                                item.MatchedDocumentType,
                                item.AccountsReceivableReceiptId,
                                item.AccountsPayablePaymentId,
                                item.Notes,
                                item.CustomerRefundId)),
                        "OVERRIDE" => OverrideItem(
                            item.ReceiptReconciliationItemId,
                            new OverrideReconciliationItemRequest(
                                request.ActorUserId,
                                item.MatchedDocumentType,
                                item.AccountsReceivableReceiptId,
                                item.AccountsPayablePaymentId,
                                item.ApprovalRoleCode ?? throw new InvalidOperationException("Bulk override requires approval role code."),
                                item.OverrideReasonCode ?? throw new InvalidOperationException("Bulk override requires override reason code."),
                                item.Notes ?? throw new InvalidOperationException("Bulk override requires notes."),
                                item.CustomerRefundId)),
                        _ => throw new InvalidOperationException("Bulk reconciliation resolution only supports MANUAL_MATCH and OVERRIDE actions.")
                    };

                    return new BulkResolveReconciliationItemResultResponse(
                        item.ReceiptReconciliationItemId,
                        normalizedActionCode,
                        true,
                        null,
                        resolvedItem);
                }
                catch (InvalidOperationException exception)
                {
                    return new BulkResolveReconciliationItemResultResponse(
                        item.ReceiptReconciliationItemId,
                        normalizedActionCode,
                        false,
                        exception.Message,
                        null);
                }
            })
            .ToArray();

        return new BulkResolveReconciliationItemsResponse(
            request.Items.Count,
            results.Count(item => item.Succeeded),
            results.Count(item => !item.Succeeded),
            results);
    }

    public ReconciliationItemResponse ManualMatch(long receiptReconciliationItemId, ManualMatchReconciliationItemRequest request) =>
        Map(_repository.ManualMatch(
            receiptReconciliationItemId,
            new ManualMatchReconciliationItemCommand(
                request.ActorUserId,
                request.MatchedDocumentType,
                request.AccountsReceivableReceiptId,
                request.AccountsPayablePaymentId,
                request.Notes,
                request.CustomerRefundId)));

    public ReconciliationItemResponse OverrideItem(long receiptReconciliationItemId, OverrideReconciliationItemRequest request) =>
        Map(_repository.OverrideItem(
            receiptReconciliationItemId,
            new OverrideReconciliationItemCommand(
                request.ActorUserId,
                request.MatchedDocumentType,
                request.AccountsReceivableReceiptId,
                request.AccountsPayablePaymentId,
                request.ApprovalRoleCode,
                request.OverrideReasonCode,
                request.Notes,
                request.CustomerRefundId)));

    private static BankStatementResponse Map(BankStatementDetailView statement) =>
        new(
            statement.BankStatementId,
            statement.StatementNumber,
            statement.BankAccountCode,
            statement.StatementDate,
            statement.CurrencyCode,
            statement.ImportStatusCode,
            statement.OpeningBalanceAmount,
            statement.ClosingBalanceAmount,
            statement.Lines.Select(line => new BankStatementLineResponse(
                line.BankStatementLineId,
                line.LineNumber,
                line.TransactionDate,
                line.ExternalReference,
                line.DescriptionText,
                line.DebitAmount,
                line.CreditAmount,
                line.RunningBalanceAmount,
                line.IsReconciled,
                line.AccountsReceivableReceiptId,
                line.AccountsPayablePaymentId,
                line.CustomerRefundId)).ToArray());

    private static ReconciliationRunResponse Map(ReceiptReconciliationRunDetailView run) =>
        new(
            run.ReceiptReconciliationRunId,
            run.RunNumber,
            run.AccountingPeriodId,
            run.BankStatementId,
            run.RunStatusCode,
            run.MatchedItemCount,
            run.ExceptionItemCount,
            run.UnmatchedItemCount,
            run.StartedAtUtc,
            run.CompletedAtUtc,
            run.Items.Select(item => new ReconciliationItemResponse(
                item.ReceiptReconciliationItemId,
                item.BankStatementLineId,
                item.AccountsReceivableReceiptId,
                item.AccountsPayablePaymentId,
                item.CustomerRefundId,
                item.MatchedDocumentType,
                item.MatchStatusCode,
                item.StatementAmount,
                item.MatchedDocumentAmount,
                item.DifferenceAmount,
                item.Notes,
                item.ResolutionCode,
                item.OverrideApprovalRoleCode,
                item.OverrideReasonCode,
                item.ResolvedAtUtc,
                item.ResolvedByUserId)).ToArray());

    private static ReconciliationItemResponse Map(ReceiptReconciliationItemView item) =>
        new(
            item.ReceiptReconciliationItemId,
            item.BankStatementLineId,
            item.AccountsReceivableReceiptId,
            item.AccountsPayablePaymentId,
            item.CustomerRefundId,
            item.MatchedDocumentType,
            item.MatchStatusCode,
            item.StatementAmount,
            item.MatchedDocumentAmount,
            item.DifferenceAmount,
            item.Notes,
            item.ResolutionCode,
            item.OverrideApprovalRoleCode,
            item.OverrideReasonCode,
            item.ResolvedAtUtc,
            item.ResolvedByUserId);

    private static ReconciliationExceptionQueueItemResponse Map(ReconciliationExceptionQueueItemView item) =>
        new(
            item.ReceiptReconciliationItemId,
            item.ReceiptReconciliationRunId,
            item.RunNumber,
            item.BankStatementId,
            item.BankStatementNumber,
            item.BankAccountCode,
            item.BankStatementLineId,
            item.BankStatementLineNumber,
            item.TransactionDate,
            item.ExternalReference,
            item.DescriptionText,
            item.MatchedDocumentType,
            item.MatchStatusCode,
            item.AccountsReceivableReceiptId,
            item.AccountsPayablePaymentId,
            item.CustomerRefundId,
            item.StatementAmount,
            item.MatchedDocumentAmount,
            item.DifferenceAmount,
            item.Notes,
            item.ResolutionCode,
            item.OverrideApprovalRoleCode,
            item.OverrideReasonCode,
            item.ResolvedAtUtc,
            item.ResolvedByUserId,
            item.StartedAtUtc,
            item.CompletedAtUtc);

    private static ReconciliationHistoryEntryResponse Map(ReconciliationHistoryEntryView item) =>
        new(
            item.ReceiptReconciliationItemId,
            item.ReceiptReconciliationRunId,
            item.RunNumber,
            item.BankStatementId,
            item.BankStatementNumber,
            item.BankAccountCode,
            item.BankStatementLineId,
            item.BankStatementLineNumber,
            item.TransactionDate,
            item.ExternalReference,
            item.DescriptionText,
            item.MatchedDocumentType,
            item.MatchStatusCode,
            item.StatementAmount,
            item.MatchedDocumentAmount,
            item.DifferenceAmount,
            item.Notes,
            item.ResolutionCode,
            item.OverrideApprovalRoleCode,
            item.OverrideReasonCode,
            item.CreatedAtUtc,
            item.CreatedByUserId,
            item.ResolvedAtUtc,
            item.ResolvedByUserId);

    private static ReconciliationAuditResponse Map(ReconciliationAuditView audit) =>
        new(
            Map(audit.Item),
            audit.Events.Select(Map).ToArray());

    private static ReconciliationAuditEventResponse Map(ReconciliationAuditEventView item) =>
        new(
            item.AuditEventId,
            item.ActionCode,
            item.ActorUserId,
            item.OldValue,
            item.NewValue,
            item.OccurredAtUtc);
}
