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

namespace Books.Api.EventFlow.Repositories;

public class OrderRepository(NpgsqlDataSource dataSource) : IOrderRepository
{
    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,
        order_number AS OrderNumber,
        order_date AS OrderDate,
        expected_delivery_date AS ExpectedDeliveryDate,
        status AS Status,
        amount_ex_vat AS AmountExVat,
        amount_vat AS AmountVat,
        amount_total AS AmountTotal,
        currency AS Currency,
        lines AS LinesJson,
        invoice_id AS InvoiceId,
        invoice_number AS InvoiceNumber,
        notes AS Notes,
        reference AS Reference,
        confirmed_at AS ConfirmedAt,
        confirmed_by AS ConfirmedBy,
        invoiced_at AS InvoicedAt,
        invoiced_by AS InvoicedBy,
        cancelled_at AS CancelledAt,
        cancelled_by AS CancelledBy,
        cancelled_reason AS CancelledReason,
        created_by AS CreatedBy,
        updated_by AS UpdatedBy,
        create_time AS CreatedAt,
        update_time AS UpdatedAt
        """;

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

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

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

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

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

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

        return result.ToList();
    }

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

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

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

        return result.ToList();
    }

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

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

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

        return result.ToList();
    }

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

        var sql = $"""
            SELECT {SelectColumns}
            FROM order_read_models
            WHERE company_id = @CompanyId
            AND order_number = @OrderNumber
            """;

        return await connection.QuerySingleOrDefaultAsync<OrderReadModelDto>(
            sql, new { CompanyId = companyId, OrderNumber = orderNumber });
    }

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

        // Get orders that can have lines invoiced (confirmed or partially_invoiced)
        var sql = $"""
            SELECT {SelectColumns}
            FROM order_read_models
            WHERE company_id = @CompanyId
            AND status IN ('confirmed', 'partially_invoiced')
            ORDER BY order_date ASC, order_number ASC
            """;

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

        return result.ToList();
    }

    public async Task<OrderReadModelDto?> GetByInvoiceIdAsync(
        string invoiceId,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = $"""
            SELECT {SelectColumns}
            FROM order_read_models
            WHERE invoice_id = @InvoiceId
            """;

        return await connection.QuerySingleOrDefaultAsync<OrderReadModelDto>(sql, new { InvoiceId = invoiceId });
    }

    public async Task<int> GetNextOrderNumberAsync(
        string companyId,
        int year,
        CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        // Order numbers are formatted as "ORD-YYYY-NNNN"
        var prefix = $"ORD-{year}-";

        var sql = """
            SELECT COALESCE(MAX(
                CAST(SUBSTRING(order_number FROM '\d+$') AS INTEGER)
            ), 0) + 1
            FROM order_read_models
            WHERE company_id = @CompanyId
            AND order_number LIKE @Prefix || '%'
            """;

        return await connection.QuerySingleAsync<int>(sql, new { CompanyId = companyId, Prefix = prefix });
    }
}
