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

namespace Books.Api.Banking;

/// <summary>
/// Hangfire job for syncing bank transactions from Enable Banking.
/// Runs every 30 minutes to fetch new transactions for all active bank connections.
/// </summary>
public class BankTransactionSyncJob(
    IBankConnectionRepository connectionRepository,
    IBankTransactionRepository transactionRepository,
    IEnableBankingClient bankingClient,
    ILogger<BankTransactionSyncJob> logger)
{
    /// <summary>
    /// Sync all active bank connections for all companies.
    /// Called by Hangfire recurring job.
    /// </summary>
    [DisableConcurrentExecution(timeoutInSeconds: 300)]
    [AutomaticRetry(Attempts = 3, DelaysInSeconds = [60, 120, 300])]
    public async Task SyncAllActiveConnectionsAsync(CancellationToken cancellationToken = default)
    {
        logger.LogInformation("Starting bank transaction sync for all active connections");

        var result = new BankTransactionSyncResult();

        try
        {
            // Get all companies with active connections
            // Note: This is a simplified approach - in production you might want to
            // iterate through companies more efficiently
            var connections = await GetAllActiveConnectionsAsync(cancellationToken);
            result.TotalConnections = connections.Count;

            foreach (var connection in connections)
            {
                try
                {
                    var connectionResult = await SyncConnectionAsync(connection, cancellationToken);
                    result.TotalAccounts += connectionResult.TotalAccounts;
                    result.NewTransactions += connectionResult.NewTransactions;
                    result.SkippedDuplicates += connectionResult.SkippedDuplicates;
                }
                catch (Exception ex)
                {
                    logger.LogError(ex, "Error syncing connection {ConnectionId}", connection.Id);
                    result.Errors++;
                    result.ErrorMessages.Add($"Connection {connection.Id}: {ex.Message}");
                }
            }

            logger.LogInformation(
                "Bank transaction sync completed: {Connections} connections, {Accounts} accounts, " +
                "{New} new transactions, {Skipped} duplicates, {Errors} errors",
                result.TotalConnections, result.TotalAccounts, result.NewTransactions,
                result.SkippedDuplicates, result.Errors);
        }
        catch (Exception ex)
        {
            logger.LogError(ex, "Fatal error during bank transaction sync");
            throw;
        }
    }

    /// <summary>
    /// Sync transactions for a specific company (manual trigger from UI).
    /// </summary>
    public async Task<BankTransactionSyncResult> SyncForCompanyAsync(
        string companyId,
        CancellationToken cancellationToken = default)
    {
        logger.LogInformation("Starting manual bank transaction sync for company {CompanyId}", companyId);

        var result = new BankTransactionSyncResult();

        var connections = await connectionRepository.GetActiveByCompanyIdAsync(companyId, cancellationToken);
        result.TotalConnections = connections.Count;

        foreach (var connection in connections)
        {
            try
            {
                var connectionResult = await SyncConnectionAsync(connection, cancellationToken);
                result.TotalAccounts += connectionResult.TotalAccounts;
                result.NewTransactions += connectionResult.NewTransactions;
                result.SkippedDuplicates += connectionResult.SkippedDuplicates;
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Error syncing connection {ConnectionId}", connection.Id);
                result.Errors++;
                result.ErrorMessages.Add($"Connection {connection.Id}: {ex.Message}");
            }
        }

        logger.LogInformation(
            "Manual sync for company {CompanyId} completed: {New} new transactions",
            companyId, result.NewTransactions);

        return result;
    }

    private async Task<BankTransactionSyncResult> SyncConnectionAsync(
        BankConnectionReadModelDto connection,
        CancellationToken cancellationToken)
    {
        var result = new BankTransactionSyncResult();

        if (string.IsNullOrEmpty(connection.SessionId))
        {
            logger.LogWarning("Connection {ConnectionId} has no session ID", connection.Id);
            return result;
        }

        if (connection.Accounts == null || connection.Accounts.Count == 0)
        {
            logger.LogWarning("Connection {ConnectionId} has no accounts", connection.Id);
            return result;
        }

        result.TotalAccounts = connection.Accounts.Count;

        var dateTo = DateOnly.FromDateTime(DateTime.UtcNow);

        foreach (var account in connection.Accounts)
        {
            try
            {
                // Check if we have any transactions for this account
                var hasTransactions = await transactionRepository.HasAnyAsync(account.AccountId, cancellationToken);
                
                // If new account, fetch 1 year back. If existing, just refresh last 7 days.
                var dateFrom = hasTransactions ? dateTo.AddDays(-7) : dateTo.AddDays(-365);

                logger.LogInformation(
                    "Fetching transactions for account {AccountId} from {DateFrom} to {DateTo} (hasExisting: {HasExisting})",
                    account.AccountId, dateFrom, dateTo, hasTransactions);

                var response = await bankingClient.GetTransactionsAsync(
                    connection.SessionId,
                    account.AccountId,
                    dateFrom,
                    dateTo,
                    cancellationToken);

                logger.LogInformation(
                    "Enable Banking returned {Count} transactions for account {AccountId}",
                    response.Transactions.Count, account.AccountId);

                if (response.Transactions.Count == 0)
                {
                    continue;
                }

                var transactionsToUpsert = new List<BankTransactionDto>();

                foreach (var tx in response.Transactions)
                {
                    var dto = MapToDto(tx, connection, account.AccountId);
                    transactionsToUpsert.Add(dto);
                }

                if (transactionsToUpsert.Count > 0)
                {
                    await transactionRepository.InsertBatchAsync(transactionsToUpsert, cancellationToken);
                    result.NewTransactions += transactionsToUpsert.Count; // This counts all upserts (inserts + updates)

                    logger.LogInformation(
                        "Synced {Count} transactions for account {AccountId} (from {From} to {To})",
                        transactionsToUpsert.Count, account.AccountId, dateFrom, dateTo);
                }
            }
            catch (Exception ex)
            {
                logger.LogError(ex, "Error fetching transactions for account {AccountId}", account.AccountId);
                result.Errors++;
                result.ErrorMessages.Add($"Account {account.AccountId}: {ex.Message}");
            }
        }

        return result;
    }

    private async Task<IReadOnlyList<BankConnectionReadModelDto>> GetAllActiveConnectionsAsync(
        CancellationToken cancellationToken)
    {
        return await connectionRepository.GetAllActiveAsync(cancellationToken);
    }

    private static BankTransactionDto MapToDto(
        Transaction tx,
        BankConnectionReadModelDto connection,
        string bankAccountId)
    {
        var now = DateTime.UtcNow;

        return new BankTransactionDto
        {
            Id = $"banktx-{Guid.NewGuid()}",
            CompanyId = connection.CompanyId,
            BankConnectionId = connection.Id,
            BankAccountId = bankAccountId,
            ExternalId = tx.TransactionId,
            Amount = tx.Amount,
            Currency = tx.Currency,
            TransactionDate = tx.BookingDate.ToDateTime(TimeOnly.MinValue),
            BookingDate = tx.BookingDate.ToDateTime(TimeOnly.MinValue),
            ValueDate = tx.ValueDate?.ToDateTime(TimeOnly.MinValue),
            Description = tx.RemittanceInformation,
            CounterpartyName = tx.CreditorName ?? tx.DebtorName,
            CreditorName = tx.CreditorName,
            DebtorName = tx.DebtorName,
            Reference = tx.EndToEndId,
            Status = "pending",
            CreatedAt = now,
            UpdatedAt = now
        };
    }
}
