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

namespace Books.Api.EventFlow.Repositories;

public class InvoiceRepository(NpgsqlDataSource dataSource) : IInvoiceRepository
{
    private const string SelectColumns = """
        aggregate_id AS Id,
        company_id AS CompanyId,
        fiscal_year_id AS FiscalYearId,
        customer_id AS CustomerId,
        customer_name AS CustomerName,
        customer_number AS CustomerNumber,
        invoice_number AS InvoiceNumber,
        invoice_date AS InvoiceDate,
        due_date AS DueDate,
        invoice_type AS InvoiceType,
        original_invoice_id AS OriginalInvoiceId,
        original_invoice_number AS OriginalInvoiceNumber,
        credit_reason AS CreditReason,
        status AS Status,
        amount_ex_vat AS AmountExVat,
        amount_vat AS AmountVat,
        amount_total AS AmountTotal,
        amount_paid AS AmountPaid,
        amount_applied AS AmountApplied,
        amount_remaining AS AmountRemaining,
        currency AS Currency,
        vat_code AS VatCode,
        payment_terms_days AS PaymentTermsDays,
        lines AS LinesJson,
        ledger_transaction_id AS LedgerTransactionId,
        notes AS Notes,
        reference AS Reference,
        sent_at AS SentAt,
        issued_at AS IssuedAt,
        paid_at AS PaidAt,
        voided_at AS VoidedAt,
        voided_reason AS VoidedReason,
        voided_by AS VoidedBy,
        created_by AS CreatedBy,
        updated_by AS UpdatedBy,
        create_time AS CreatedAt,
        update_time AS UpdatedAt
        """;

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

        var sql = $"""
            SELECT {SelectColumns}
            FROM invoice_read_models
            WHERE aggregate_id = @Id
            """;

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

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

        var sql = $"""
            SELECT {SelectColumns}
            FROM invoice_read_models
            WHERE company_id = @CompanyId
            {(status != null ? "AND status = @Status" : "")}
            ORDER BY invoice_date DESC, invoice_number DESC
            """;

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

        return result.ToList();
    }

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

        var sql = $"""
            SELECT {SelectColumns}
            FROM invoice_read_models
            WHERE customer_id = @CustomerId
            {(status != null ? "AND status = @Status" : "")}
            ORDER BY invoice_date DESC, invoice_number DESC
            """;

        var result = await connection.QueryAsync<InvoiceReadModelDto>(
            sql, new { CustomerId = customerId, Status = status });

        return result.ToList();
    }

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

        var sql = $"""
            SELECT {SelectColumns}
            FROM invoice_read_models
            WHERE fiscal_year_id = @FiscalYearId
            {(status != null ? "AND status = @Status" : "")}
            ORDER BY invoice_date DESC, invoice_number DESC
            """;

        var result = await connection.QueryAsync<InvoiceReadModelDto>(
            sql, new { FiscalYearId = fiscalYearId, Status = status });

        return result.ToList();
    }

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

        var sql = $"""
            SELECT {SelectColumns}
            FROM invoice_read_models
            WHERE company_id = @CompanyId
            AND status IN ('sent', 'partially_paid')
            AND amount_remaining > 0
            ORDER BY due_date ASC, invoice_number ASC
            """;

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

        return result.ToList();
    }

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

        var sql = $"""
            SELECT {SelectColumns}
            FROM invoice_read_models
            WHERE company_id = @CompanyId
            AND status IN ('sent', 'partially_paid')
            AND amount_remaining > 0
            AND due_date < @AsOfDate
            ORDER BY due_date ASC, invoice_number ASC
            """;

        var result = await connection.QueryAsync<InvoiceReadModelDto>(
            sql, new { CompanyId = companyId, AsOfDate = asOfDate.ToDateTime(TimeOnly.MinValue) });

        return result.ToList();
    }

    public async Task<InvoiceReadModelDto?> GetByInvoiceNumberAsync(
        string companyId,
        string invoiceNumber,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = $"""
            SELECT {SelectColumns}
            FROM invoice_read_models
            WHERE company_id = @CompanyId
            AND invoice_number = @InvoiceNumber
            """;

        return await connection.QuerySingleOrDefaultAsync<InvoiceReadModelDto>(
            sql, new { CompanyId = companyId, InvoiceNumber = invoiceNumber });
    }

    // Credit note specific queries

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

        var sql = $"""
            SELECT {SelectColumns}
            FROM invoice_read_models
            WHERE company_id = @CompanyId
            AND invoice_type = 'credit_note'
            {(status != null ? "AND status = @Status" : "")}
            ORDER BY invoice_date DESC, invoice_number DESC
            """;

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

        return result.ToList();
    }

    public async Task<IReadOnlyList<InvoiceReadModelDto>> GetCreditNotesForInvoiceAsync(
        string invoiceId,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = $"""
            SELECT {SelectColumns}
            FROM invoice_read_models
            WHERE original_invoice_id = @InvoiceId
            AND invoice_type = 'credit_note'
            ORDER BY invoice_date DESC
            """;

        var result = await connection.QueryAsync<InvoiceReadModelDto>(sql, new { InvoiceId = invoiceId });

        return result.ToList();
    }

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

        var sql = $"""
            SELECT {SelectColumns}
            FROM invoice_read_models
            WHERE company_id = @CompanyId
            AND invoice_type = 'credit_note'
            AND status IN ('issued', 'partially_applied')
            AND amount_remaining > 0
            ORDER BY invoice_date ASC
            """;

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

        return result.ToList();
    }

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

        var sql = $"""
            SELECT {SelectColumns}
            FROM invoice_read_models
            WHERE company_id = @CompanyId
            AND (invoice_type = 'invoice' OR invoice_type IS NULL)
            {(status != null ? "AND status = @Status" : "")}
            ORDER BY invoice_date DESC, invoice_number DESC
            """;

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

        return result.ToList();
    }
}
