using Books.Api.EventFlow.ReadModels;
using Dapper;
using Npgsql;

namespace Books.Api.EventFlow.Repositories;

public class BankTransactionRepository(NpgsqlDataSource dataSource) : IBankTransactionRepository
{
    private const string SelectColumns = """
        id AS Id,
        company_id AS CompanyId,
        bank_connection_id AS BankConnectionId,
        bank_account_id AS BankAccountId,
        external_id AS ExternalId,
        amount AS Amount,
        currency AS Currency,
        transaction_date AS TransactionDate,
        booking_date AS BookingDate,
        value_date AS ValueDate,
        description AS Description,
        counterparty_name AS CounterpartyName,
        counterparty_account AS CounterpartyAccount,
        reference AS Reference,
        creditor_name AS CreditorName,
        debtor_name AS DebtorName,
        status AS Status,
        journal_entry_draft_id AS JournalEntryDraftId,
        created_at AS CreatedAt,
        updated_at AS UpdatedAt
        """;

    public async Task<IReadOnlyList<BankTransactionDto>> GetPendingByCompanyIdAsync(
        string companyId,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = $"""
            SELECT {SelectColumns}
            FROM bank_transactions
            WHERE company_id = @CompanyId AND status = 'pending'
            ORDER BY transaction_date DESC, created_at DESC
            """;

        var result = await connection.QueryAsync<BankTransactionDto>(
            sql,
            new { CompanyId = companyId });

        return result.ToList();
    }

    public async Task<IReadOnlyList<BankTransactionDto>> GetByCompanyIdAsync(
        string companyId,
        string? status = null,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = status != null
            ? $"""
                SELECT {SelectColumns}
                FROM bank_transactions
                WHERE company_id = @CompanyId AND status = @Status
                ORDER BY transaction_date DESC, created_at DESC
                """
            : $"""
                SELECT {SelectColumns}
                FROM bank_transactions
                WHERE company_id = @CompanyId
                ORDER BY transaction_date DESC, created_at DESC
                """;

        var result = await connection.QueryAsync<BankTransactionDto>(
            sql,
            new { CompanyId = companyId, Status = status });

        return result.ToList();
    }

    public async Task<BankTransactionDto?> GetByIdAsync(
        string id,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = $"""
            SELECT {SelectColumns}
            FROM bank_transactions
            WHERE id = @Id
            """;

        return await connection.QuerySingleOrDefaultAsync<BankTransactionDto>(
            sql,
            new { Id = id });
    }

    public async Task<bool> ExistsAsync(
        string companyId,
        string externalId,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = """
            SELECT EXISTS(
                SELECT 1 FROM bank_transactions
                WHERE company_id = @CompanyId AND external_id = @ExternalId
            )
            """;

        return await connection.ExecuteScalarAsync<bool>(
            sql,
            new { CompanyId = companyId, ExternalId = externalId });
    }

    public async Task InsertAsync(
        BankTransactionDto transaction,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = """
            INSERT INTO bank_transactions (
                id, company_id, bank_connection_id, bank_account_id, external_id,
                amount, currency, transaction_date, booking_date, value_date,
                description, counterparty_name, counterparty_account, reference,
                creditor_name, debtor_name, status, journal_entry_draft_id,
                created_at, updated_at
            ) VALUES (
                @Id, @CompanyId, @BankConnectionId, @BankAccountId, @ExternalId,
                @Amount, @Currency, @TransactionDate, @BookingDate, @ValueDate,
                @Description, @CounterpartyName, @CounterpartyAccount, @Reference,
                @CreditorName, @DebtorName, @Status, @JournalEntryDraftId,
                @CreatedAt, @UpdatedAt
            )
            """;

        await connection.ExecuteAsync(sql, transaction);
    }

    public async Task InsertBatchAsync(
        IEnumerable<BankTransactionDto> transactions,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);
        await using var transaction = await connection.BeginTransactionAsync(cancellationToken);

        try
        {
            var sql = """
                INSERT INTO bank_transactions (
                    id, company_id, bank_connection_id, bank_account_id, external_id,
                    amount, currency, transaction_date, booking_date, value_date,
                    description, counterparty_name, counterparty_account, reference,
                    creditor_name, debtor_name, status, journal_entry_draft_id,
                    created_at, updated_at
                ) VALUES (
                    @Id, @CompanyId, @BankConnectionId, @BankAccountId, @ExternalId,
                    @Amount, @Currency, @TransactionDate, @BookingDate, @ValueDate,
                    @Description, @CounterpartyName, @CounterpartyAccount, @Reference,
                    @CreditorName, @DebtorName, @Status, @JournalEntryDraftId,
                    @CreatedAt, @UpdatedAt
                )
                ON CONFLICT (company_id, external_id) DO UPDATE SET
                    booking_date = EXCLUDED.booking_date,
                    value_date = EXCLUDED.value_date,
                    description = EXCLUDED.description,
                    counterparty_name = EXCLUDED.counterparty_name,
                    reference = EXCLUDED.reference,
                    creditor_name = EXCLUDED.creditor_name,
                    debtor_name = EXCLUDED.debtor_name,
                    updated_at = NOW()
                WHERE bank_transactions.status = 'pending'
                """;

            await connection.ExecuteAsync(sql, transactions, transaction);
            await transaction.CommitAsync(cancellationToken);
        }
        catch
        {
            await transaction.RollbackAsync(cancellationToken);
            throw;
        }
    }

    public async Task UpdateStatusAsync(
        string id,
        string status,
        string? journalEntryDraftId,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = """
            UPDATE bank_transactions
            SET status = @Status,
                journal_entry_draft_id = @JournalEntryDraftId,
                updated_at = NOW()
            WHERE id = @Id
            """;

        await connection.ExecuteAsync(
            sql,
            new { Id = id, Status = status, JournalEntryDraftId = journalEntryDraftId });
    }

    public async Task<IReadOnlyList<BankTransactionDto>> GetByBankAccountIdAsync(
        string bankAccountId,
        string? status = null,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = status != null
            ? $"""
                SELECT {SelectColumns}
                FROM bank_transactions
                WHERE bank_account_id = @BankAccountId AND status = @Status
                ORDER BY transaction_date DESC
                """
            : $"""
                SELECT {SelectColumns}
                FROM bank_transactions
                WHERE bank_account_id = @BankAccountId
                ORDER BY transaction_date DESC
                """;

        var result = await connection.QueryAsync<BankTransactionDto>(
            sql,
            new { BankAccountId = bankAccountId, Status = status });

        return result.ToList();
    }

    public async Task<bool> HasAnyAsync(string bankAccountId, CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = "SELECT EXISTS(SELECT 1 FROM bank_transactions WHERE bank_account_id = @BankAccountId)";

        return await connection.ExecuteScalarAsync<bool>(
            sql,
            new { BankAccountId = bankAccountId });
    }
}
