using Books.Api.EventFlow.ReadModels;
using Books.Api.EventFlow.Repositories;

namespace Books.Api.AiBookkeeper;

/// <summary>
/// Matches documents to pending bank transactions based on amount.
/// </summary>
public class BankTransactionMatcher(
    IBankTransactionRepository transactionRepository,
    ILogger<BankTransactionMatcher> logger) : IBankTransactionMatcher
{
    public async Task<BankTransactionDto?> FindMatchingTransactionAsync(
        string companyId,
        decimal amount,
        decimal tolerance = 0.01m,
        CancellationToken cancellationToken = default)
    {
        // For invoice/expense: document shows positive amount, but bank transaction is negative (money leaving)
        // We need to match on the absolute value
        var targetAmount = amount;

        logger.LogDebug(
            "Searching for pending transaction matching amount {Amount} (±{Tolerance}) for company {CompanyId}",
            targetAmount, tolerance, companyId);

        var pendingTransactions = await transactionRepository.GetPendingByCompanyIdAsync(companyId, cancellationToken);

        if (pendingTransactions.Count == 0)
        {
            logger.LogDebug("No pending transactions found for company {CompanyId}", companyId);
            return null;
        }

        // Find transactions matching the amount within tolerance
        // For expenses: document amount is positive, bank amount is negative
        // So we compare: |bank amount| matches document amount
        var matchingTransactions = pendingTransactions
            .Where(t => IsAmountMatch(t.Amount, targetAmount, tolerance))
            .OrderBy(t => t.TransactionDate) // Oldest first
            .ThenBy(t => t.CreatedAt)
            .ToList();

        if (matchingTransactions.Count == 0)
        {
            logger.LogDebug(
                "No matching transactions found for amount {Amount} (checked {Count} pending)",
                targetAmount, pendingTransactions.Count);
            return null;
        }

        var match = matchingTransactions.First();
        logger.LogInformation(
            "Found matching transaction {TransactionId}: {Amount} on {Date} ({Description})",
            match.Id, match.Amount, match.TransactionDate.ToString("yyyy-MM-dd"), match.Description);

        if (matchingTransactions.Count > 1)
        {
            logger.LogDebug(
                "Multiple matches found ({Count}), returning oldest",
                matchingTransactions.Count);
        }

        return match;
    }

    private static bool IsAmountMatch(decimal bankAmount, decimal documentAmount, decimal tolerance)
    {
        // For expenses: document amount is typically positive (e.g., invoice for 1000 DKK)
        // Bank transaction is negative (e.g., -1000 DKK outgoing payment)
        // So we need to check if the absolute values match

        // Option 1: Exact match on absolute values (most common for invoice matching)
        var absBank = Math.Abs(bankAmount);
        var absDoc = Math.Abs(documentAmount);

        if (Math.Abs(absBank - absDoc) <= tolerance)
        {
            return true;
        }

        // Option 2: Direct match (same sign and value)
        if (Math.Abs(bankAmount - documentAmount) <= tolerance)
        {
            return true;
        }

        // Option 3: Opposite sign match (document positive, bank negative or vice versa)
        if (Math.Abs(bankAmount + documentAmount) <= tolerance)
        {
            return true;
        }

        return false;
    }
}
