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

namespace Books.Api.EventFlow.Repositories;

public class FiscalYearRepository(NpgsqlDataSource dataSource) : IFiscalYearRepository
{
    private const string SelectColumns = """
        aggregate_id AS Id,
        company_id AS CompanyId,
        name AS Name,
        start_date AS StartDate,
        end_date AS EndDate,
        status AS Status,
        opening_balance_posted AS OpeningBalancePosted,
        closing_date AS ClosingDate,
        closed_by AS ClosedBy,
        reopened_date AS ReopenedDate,
        reopened_by AS ReopenedBy,
        locked_date AS LockedDate,
        locked_by AS LockedBy,
        create_time AS CreatedAt,
        updated_time AS UpdatedAt
        """;

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

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

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

    public async Task<IEnumerable<FiscalYearReadModelDto>> GetByIdsAsync(List<FiscalYearId> ids, CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = $"""
            SELECT {SelectColumns}
            FROM fiscal_year_read_models
            WHERE aggregate_id = ANY(@Ids)
            ORDER BY start_date DESC
            """;

        return await connection.QueryAsync<FiscalYearReadModelDto>(sql, new { Ids = ids.Select(i => i.Value).ToArray() });
    }

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

        var sql = $"""
            SELECT {SelectColumns}
            FROM fiscal_year_read_models
            WHERE company_id = @CompanyId
            ORDER BY start_date DESC
            """;

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

    public async Task<FiscalYearReadModelDto?> GetByCompanyAndDateAsync(string companyId, DateOnly date, CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        var sql = $"""
            SELECT {SelectColumns}
            FROM fiscal_year_read_models
            WHERE company_id = @CompanyId
              AND start_date <= @Date
              AND end_date >= @Date
            """;

        return await connection.QuerySingleOrDefaultAsync<FiscalYearReadModelDto>(sql, new { CompanyId = companyId, Date = date.ToDateTime(TimeOnly.MinValue) });
    }

    public async Task<bool> HasOverlappingYearAsync(string companyId, DateOnly startDate, DateOnly endDate, string? excludeId = null, CancellationToken cancellationToken = default)
    {
        await using var connection = await dataSource.OpenConnectionAsync(cancellationToken);

        // Overlap detection: two ranges overlap if start1 <= end2 AND end1 >= start2
        // This correctly handles boundary cases where fiscal years share a date
        var sql = """
            SELECT EXISTS(
                SELECT 1
                FROM fiscal_year_read_models
                WHERE company_id = @CompanyId
                  AND (@ExcludeId IS NULL OR aggregate_id != @ExcludeId)
                  AND start_date <= @EndDate
                  AND end_date >= @StartDate
            )
            """;

        return await connection.QuerySingleAsync<bool>(sql, new
        {
            CompanyId = companyId,
            StartDate = startDate.ToDateTime(TimeOnly.MinValue),
            EndDate = endDate.ToDateTime(TimeOnly.MinValue),
            ExcludeId = excludeId
        });
    }
}
