// src/SIGCM.Infrastructure/Repositories/ListingRepository.cs
using Dapper;
using SIGCM.Application.DTOs;
using SIGCM.Domain.Entities;
using SIGCM.Domain.Interfaces;
using SIGCM.Domain.Models;
using SIGCM.Infrastructure.Data;

namespace SIGCM.Infrastructure.Repositories;

public class ListingRepository : IListingRepository
{
    private readonly IDbConnectionFactory _connectionFactory;

    public ListingRepository(IDbConnectionFactory connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }

    public async Task<int> CreateAsync(Listing listing, Dictionary<int, string> attributes, List<Payment>? payments = null)
    {
        using var conn = _connectionFactory.CreateConnection();
        conn.Open();
        using var transaction = conn.BeginTransaction();

        try
        {
            var sqlListing = @"
                INSERT INTO Listings (
                    CategoryId, OperationId, Title, Description, Price, Currency, 
                    CreatedAt, Status, UserId, PrintText, PrintStartDate, PrintDaysCount,
                    IsBold, IsFrame, PrintFontSize, PrintAlignment, AdFee, ClientId,
                    PublicationStartDate, IsFeatured, FeaturedExpiry, AllowContact, CouponCode, Origin
                )
                VALUES (
                    @CategoryId, @OperationId, @Title, @Description, @Price, @Currency, 
                    @CreatedAt, @Status, @UserId, @PrintText, @PrintStartDate, @PrintDaysCount,
                    @IsBold, @IsFrame, @PrintFontSize, @PrintAlignment, @AdFee, @ClientId,
                    @PublicationStartDate, @IsFeatured, @FeaturedExpiry, @AllowContact, @CouponCode, @Origin
                );
                SELECT CAST(SCOPE_IDENTITY() as int);";

            var listingId = await conn.QuerySingleAsync<int>(sqlListing, listing, transaction);

            if (attributes != null && attributes.Any())
            {
                var sqlAttr = @"
                    INSERT INTO ListingAttributeValues (ListingId, AttributeDefinitionId, Value)
                    VALUES (@ListingId, @AttributeDefinitionId, @Value)";

                foreach (var attr in attributes)
                {
                    await conn.ExecuteAsync(sqlAttr, new { ListingId = listingId, AttributeDefinitionId = attr.Key, Value = attr.Value }, transaction);
                }
            }

            if (listing.ImagesToClone != null && listing.ImagesToClone.Any())
            {
                foreach (var url in listing.ImagesToClone)
                {
                    // Validamos que la URL sea válida antes de insertar
                    if (string.IsNullOrEmpty(url)) continue;

                    var sqlImg = @"INSERT INTO ListingImages (ListingId, Url, IsMainInfo, DisplayOrder) 
                       VALUES (@listingId, @url, 0, 0)";

                    // Usamos el ID del nuevo aviso (listingId) y la URL del viejo
                    await conn.ExecuteAsync(sqlImg, new { listingId, url }, transaction);
                }
            }

            if (payments != null && payments.Any())
            {
                var sqlPayment = @"
                    INSERT INTO Payments (ListingId, Amount, PaymentMethod, CardPlan, Surcharge, PaymentDate)
                    VALUES (@ListingId, @Amount, @PaymentMethod, @CardPlan, @Surcharge, @PaymentDate)";

                foreach (var pay in payments)
                {
                    pay.ListingId = listingId;
                    await conn.ExecuteAsync(sqlPayment, pay, transaction);
                }
            }

            transaction.Commit();
            return listingId;
        }
        catch
        {
            transaction.Rollback();
            throw;
        }
    }

    public async Task<Listing?> GetByIdAsync(int id)
    {
        using var conn = _connectionFactory.CreateConnection();
        return await conn.QuerySingleOrDefaultAsync<Listing>("SELECT * FROM Listings WHERE Id = @Id", new { Id = id });
    }

    public async Task<ListingDetail?> GetDetailByIdAsync(int id)
    {
        using var conn = _connectionFactory.CreateConnection();
        var sql = @"
            SELECT 
                l.*, 
                c.Name as CategoryName, 
                p.Name as ParentCategoryName,
                cl.BillingName as ClientName, cl.BillingTaxId as ClientDni,
                u.Username as CashierName
            FROM Listings l
            LEFT JOIN Categories c ON l.CategoryId = c.Id
            LEFT JOIN Categories p ON c.ParentId = p.Id
            LEFT JOIN Users cl ON l.ClientId = cl.Id
            LEFT JOIN Users u ON l.UserId = u.Id
            WHERE l.Id = @Id;
            
            SELECT lav.*, ad.Name as AttributeName 
            FROM ListingAttributeValues lav
            JOIN AttributeDefinitions ad ON lav.AttributeDefinitionId = ad.Id
            WHERE lav.ListingId = @Id;
        
            SELECT * FROM ListingImages WHERE ListingId = @Id ORDER BY DisplayOrder;
        
            SELECT * FROM Payments WHERE ListingId = @Id;
        ";

        using var multi = await conn.QueryMultipleAsync(sql, new { Id = id });
        var listingData = await multi.ReadSingleOrDefaultAsync<dynamic>();
        if (listingData == null) return null;

        var attributes = await multi.ReadAsync<ListingAttributeValueWithName>();
        var images = await multi.ReadAsync<ListingImage>();
        var payments = await multi.ReadAsync<Payment>();

        return new ListingDetail
        {
            Listing = new Listing
            {
                Id = (int)listingData.Id,
                CategoryId = (int)(listingData.CategoryId ?? 0),
                OperationId = (int)(listingData.OperationId ?? 0),
                Title = listingData.Title ?? "",
                Description = listingData.Description ?? "",
                Price = listingData.Price ?? 0m,
                AdFee = listingData.AdFee ?? 0m,
                Status = listingData.Status ?? "Draft",
                CreatedAt = listingData.CreatedAt,
                UserId = listingData.UserId,
                CategoryName = listingData.CategoryName,
                ParentCategoryName = listingData.ParentCategoryName,
                PrintDaysCount = (int)(listingData.PrintDaysCount ?? 0),
                PrintText = listingData.PrintText,
                IsBold = listingData.IsBold != null && Convert.ToBoolean(listingData.IsBold),
                IsFrame = listingData.IsFrame != null && Convert.ToBoolean(listingData.IsFrame),
                PublicationStartDate = listingData.PublicationStartDate,
                ApprovedAt = listingData.ApprovedAt,
                IsFeatured = listingData.IsFeatured != null && Convert.ToBoolean(listingData.IsFeatured),
                FeaturedExpiry = listingData.FeaturedExpiry,
                AllowContact = listingData.AllowContact != null && Convert.ToBoolean(listingData.AllowContact),
            },
            Attributes = attributes,
            Images = images,
            Payments = payments
        };
    }

    public async Task<IEnumerable<Listing>> GetAllAsync()
    {
        using var conn = _connectionFactory.CreateConnection();
        var sql = @"
            SELECT l.*, c.Name as CategoryName,
                   (SELECT TOP 1 Url FROM ListingImages li WHERE li.ListingId = l.Id ORDER BY IsMainInfo DESC, DisplayOrder ASC) as MainImageUrl
            FROM Listings l
            JOIN Categories c ON l.CategoryId = c.Id
            WHERE l.Status = 'Published'
              AND (l.PublicationStartDate IS NULL OR l.PublicationStartDate <= GETUTCDATE())
              AND (l.PrintDaysCount = 0 OR DATEADD(day, l.PrintDaysCount, COALESCE(l.PublicationStartDate, l.ApprovedAt, l.CreatedAt)) >= GETUTCDATE())
            ORDER BY l.CreatedAt DESC";

        return await conn.QueryAsync<Listing>(sql);
    }

    // Implementación de incremento
    public async Task IncrementViewCountAsync(int id)
    {
        using var conn = _connectionFactory.CreateConnection();
        await conn.ExecuteAsync("UPDATE Listings SET ViewCount = ViewCount + 1 WHERE Id = @Id", new { Id = id });
    }

    public async Task<IEnumerable<Listing>> GetByUserIdAsync(int userId)
    {
        using var conn = _connectionFactory.CreateConnection();
        var sql = @"
            SELECT l.*, c.Name as CategoryName,
                   (SELECT TOP 1 Url FROM ListingImages li WHERE li.ListingId = l.Id ORDER BY IsMainInfo DESC, DisplayOrder ASC) as MainImageUrl
            FROM Listings l
            JOIN Categories c ON l.CategoryId = c.Id
            WHERE l.UserId = @UserId
            ORDER BY l.CreatedAt DESC";

        return await conn.QueryAsync<Listing>(sql, new { UserId = userId });
    }

    public async Task<IEnumerable<Listing>> SearchAsync(string? query, int? categoryId)
    {
        return await SearchFacetedAsync(query, categoryId, null, null, null, null, null, true);
    }

    // Búsqueda Avanzada Facetada
    public async Task<IEnumerable<Listing>> SearchFacetedAsync(
    string? query,
    int? categoryId,
    Dictionary<string, string>? attributes,
    DateTime? from = null,
    DateTime? to = null,
    string? origin = null,
    string? status = null,
    bool onlyActive = false)
    {
        using var conn = _connectionFactory.CreateConnection();
        var parameters = new DynamicParameters();

        string sql = @"
            SELECT l.*, c.Name as CategoryName, cl.BillingName as ClientName, cl.BillingTaxId as ClientDni,
                   (SELECT TOP 1 Url FROM ListingImages li WHERE li.ListingId = l.Id ORDER BY IsMainInfo DESC, DisplayOrder ASC) as MainImageUrl
            FROM Listings l
            JOIN Categories c ON l.CategoryId = c.Id
            LEFT JOIN Users cl ON l.ClientId = cl.Id
            WHERE 1=1";

        // --- FILTROS EXISTENTES ---
        if (!string.IsNullOrEmpty(query))
        {
            sql += " AND (l.Title LIKE @Query OR l.Description LIKE @Query OR cl.BillingTaxId = @ExactQuery OR cl.BillingName LIKE @Query)";
            parameters.Add("Query", $"%{query}%");
            parameters.Add("ExactQuery", query);
        }

        if (categoryId.HasValue && categoryId.Value > 0)
        {
            sql += " AND l.CategoryId = @CategoryId";
            parameters.Add("CategoryId", categoryId);
        }

        if (from.HasValue)
        {
            sql += " AND l.CreatedAt >= @From";
            parameters.Add("From", from.Value.Date);
        }

        if (to.HasValue)
        {
            sql += " AND l.CreatedAt <= @To";
            parameters.Add("To", to.Value.Date.AddDays(1).AddSeconds(-1));
        }

        if (!string.IsNullOrEmpty(origin) && origin != "All")
        {
            sql += " AND l.Origin = @Origin";
            parameters.Add("Origin", origin);
        }

        // --- FILTRO DE ESTADO ---
        if (!string.IsNullOrEmpty(status))
        {
            sql += " AND l.Status = @Status";
            parameters.Add("Status", status);
        }

        // --- FILTRO DE VISIBILIDAD (Solo para búsquedas públicas) ---
        if (onlyActive)
        {
            // Solo avisos publicados y dentro de su rango de vigencia
            sql += @" AND l.Status = 'Published'
                      AND (l.PublicationStartDate IS NULL OR l.PublicationStartDate <= GETUTCDATE())
                      AND (l.PrintDaysCount = 0 OR DATEADD(day, l.PrintDaysCount, COALESCE(l.PublicationStartDate, l.ApprovedAt, l.CreatedAt)) >= GETUTCDATE())";
        }

        sql += " ORDER BY l.IsFeatured DESC, l.CreatedAt DESC";

        return await conn.QueryAsync<Listing>(sql, parameters);
    }

    public async Task<IEnumerable<Listing>> GetListingsForPrintAsync(DateTime targetDate)
    {
        using var conn = _connectionFactory.CreateConnection();
        var sql = @"
            SELECT l.*, c.Name as CategoryName 
            FROM Listings l
            JOIN Categories c ON l.CategoryId = c.Id
            WHERE l.PrintStartDate IS NOT NULL 
              AND @TargetDate >= CAST(l.PrintStartDate AS DATE)
              AND @TargetDate < DATEADD(day, l.PrintDaysCount, CAST(l.PrintStartDate AS DATE))
            ORDER BY c.Name, l.Title";

        return await conn.QueryAsync<Listing>(sql, new { TargetDate = targetDate.Date });
    }

    public async Task<IEnumerable<Listing>> GetPendingModerationAsync()
    {
        using var conn = _connectionFactory.CreateConnection();
        // Avisos que NO están publicados ni rechazados ni borrados.
        // Asumimos 'Pending'. Incluimos conteo de notas no leídas del usuario.
        var sql = @"
            SELECT l.*, 
                   (SELECT COUNT(*) FROM ListingNotes ln WHERE ln.ListingId = l.Id AND ln.IsFromModerator = 0 AND ln.IsRead = 0) as UnreadNotesCount
            FROM Listings l 
            WHERE l.Status = 'Pending' 
            ORDER BY l.CreatedAt ASC";
        return await conn.QueryAsync<Listing>(sql);
    }

    public async Task UpdateStatusAsync(int id, string status)
    {
        using var conn = _connectionFactory.CreateConnection();
        var sql = @"
            UPDATE Listings 
            SET Status = @Status,
                ApprovedAt = CASE 
                    WHEN (@Status = 'Published' OR @Status = 'Approved') AND ApprovedAt IS NULL THEN GETUTCDATE() 
                    ELSE ApprovedAt 
                END
            WHERE Id = @Id";
        await conn.ExecuteAsync(sql, new { Id = id, Status = status });
    }

    public async Task<int> CountByCategoryIdAsync(int categoryId)
    {
        using var conn = _connectionFactory.CreateConnection();
        return await conn.ExecuteScalarAsync<int>("SELECT COUNT(1) FROM Listings WHERE CategoryId = @Id", new { Id = categoryId });
    }

    public async Task MoveListingsAsync(int sourceCategoryId, int targetCategoryId)
    {
        using var conn = _connectionFactory.CreateConnection();
        // Solo movemos los avisos, no tocamos la estructura de categorías
        await conn.ExecuteAsync(
            "UPDATE Listings SET CategoryId = @TargetId WHERE CategoryId = @SourceId",
            new { SourceId = sourceCategoryId, TargetId = targetCategoryId });
    }

    public async Task<IEnumerable<CategorySalesReportDto>> GetSalesByRootCategoryAsync(DateTime startDate, DateTime endDate)
    {
        using var conn = _connectionFactory.CreateConnection();

        // SQL con CTE Recursiva:
        // 1. Mapeamos TODA la jerarquía para saber cuál es el ID raíz de cada subcategoría.
        // 2. Unimos con Listings y sumamos.
        var sql = @"
        WITH CategoryHierarchy AS (
            -- Caso base: Categorías Raíz (donde ParentId es NULL)
            SELECT Id, Id as RootId, Name
            FROM Categories
            WHERE ParentId IS NULL

            UNION ALL

            -- Caso recursivo: Hijos que se vinculan a su padre
            SELECT c.Id, ch.RootId, ch.Name
            FROM Categories c
            INNER JOIN CategoryHierarchy ch ON c.ParentId = ch.Id
        )
        SELECT 
            ch.RootId as CategoryId, 
            ch.Name as CategoryName, 
            SUM(l.Price) as TotalSales,
            COUNT(l.Id) as AdCount
        FROM Listings l
        INNER JOIN CategoryHierarchy ch ON l.CategoryId = ch.Id
        WHERE l.CreatedAt >= @StartDate AND l.CreatedAt <= @EndDate
          AND l.Status = 'Published'
        GROUP BY ch.RootId, ch.Name
        ORDER BY TotalSales DESC";

        return await conn.QueryAsync<CategorySalesReportDto>(sql, new { StartDate = startDate, EndDate = endDate });
    }

    public async Task<int> GetPendingCountAsync()
    {
        using var conn = _connectionFactory.CreateConnection();
        // Contamos solo avisos en estado 'Pending'
        return await conn.ExecuteScalarAsync<int>("SELECT COUNT(1) FROM Listings WHERE Status = 'Pending'");
    }

    public async Task<DashboardStats> GetDashboardStatsAsync(DateTime startDate, DateTime endDate)
    {
        using var conn = _connectionFactory.CreateConnection();
        var stats = new DashboardStats();

        // 1. KPIs del periodo seleccionado
        var kpisSql = @"
        SELECT 
            CAST(ISNULL(SUM(AdFee), 0) AS DECIMAL(18,2)) as RevenueToday,
            COUNT(Id) as AdsToday
        FROM Listings 
        WHERE CAST(CreatedAt AS DATE) >= @StartDate 
          AND CAST(CreatedAt AS DATE) <= @EndDate
          AND Status = 'Published'";

        var kpis = await conn.QueryFirstOrDefaultAsync(kpisSql, new { StartDate = startDate.Date, EndDate = endDate.Date });
        stats.RevenueToday = kpis?.RevenueToday ?? 0;
        stats.AdsToday = kpis?.AdsToday ?? 0;
        stats.TicketAverage = stats.AdsToday > 0 ? stats.RevenueToday / stats.AdsToday : 0;

        // 2. Occupation (based on the last day of the range)
        stats.PaperOccupation = Math.Min(100, (stats.AdsToday * 100.0) / 100.0);

        // 3. Tendencia del periodo
        // Si el rango es mayor a 10 días, agrupamos diferente, pero por ahora mantenemos ddd
        var trendSql = @"
        SELECT 
            FORMAT(CreatedAt, 'dd/MM') as Day,
            SUM(AdFee) as Amount
        FROM Listings
        WHERE CAST(CreatedAt AS DATE) >= @StartDate 
          AND CAST(CreatedAt AS DATE) <= @EndDate
          AND Status = 'Published'
        GROUP BY FORMAT(CreatedAt, 'dd/MM'), CAST(CreatedAt AS DATE)
        ORDER BY CAST(CreatedAt AS DATE) ASC";

        var trendResult = await conn.QueryAsync<DailyRevenue>(trendSql, new { StartDate = startDate.Date, EndDate = endDate.Date });
        stats.WeeklyTrend = trendResult.ToList();

        // 4. Mix de Canales del periodo
        var channelsSql = @"
        SELECT 
            CASE WHEN UserId IS NULL THEN 'Web' ELSE 'Mostrador' END as Name,
            COUNT(Id) as Value
        FROM Listings
        WHERE CAST(CreatedAt AS DATE) >= @StartDate 
          AND CAST(CreatedAt AS DATE) <= @EndDate
          AND Status = 'Published'
        GROUP BY CASE WHEN UserId IS NULL THEN 'Web' ELSE 'Mostrador' END";

        var channelResult = await conn.QueryAsync<ChannelStat>(channelsSql, new { StartDate = startDate.Date, EndDate = endDate.Date });
        stats.ChannelMix = channelResult.ToList();

        return stats;
    }

    public async Task<AdvancedAnalyticsDto> GetAdvancedAnalyticsAsync(DateTime startDate, DateTime endDate)
    {
        using var conn = _connectionFactory.CreateConnection();
        var analytics = new AdvancedAnalyticsDto();

        // 1. Calcular Periodo Anterior para comparación
        var duration = endDate - startDate;
        var prevStart = startDate.Add(-duration);
        var prevEnd = startDate.AddSeconds(-1);

        // 2. KPIs del Periodo Actual
        var currentKpiSql = @"
        SELECT 
            CAST(ISNULL(SUM(AdFee), 0) AS DECIMAL(18,2)) as Revenue,
            COUNT(Id) as Ads
        FROM Listings 
        WHERE CAST(CreatedAt AS DATE) BETWEEN @Start AND @End
          AND Status = 'Published'";

        var currentKpis = await conn.QueryFirstOrDefaultAsync(currentKpiSql, new { Start = startDate.Date, End = endDate.Date });
        analytics.TotalRevenue = currentKpis?.Revenue ?? 0;
        analytics.TotalAds = currentKpis?.Ads ?? 0;

        // 3. KPIs del Periodo Anterior
        var prevKpiSql = @"
        SELECT 
            CAST(ISNULL(SUM(AdFee), 0) AS DECIMAL(18,2)) as Revenue,
            COUNT(Id) as Ads
        FROM Listings 
        WHERE CAST(CreatedAt AS DATE) BETWEEN @PrevStart AND @PrevEnd
          AND Status = 'Published'";

        var prevKpis = await conn.QueryFirstOrDefaultAsync(prevKpiSql, new { PrevStart = prevStart.Date, PrevEnd = prevEnd.Date });
        analytics.PreviousPeriodRevenue = prevKpis?.Revenue ?? 0;
        analytics.PreviousPeriodAds = prevKpis?.Ads ?? 0;

        // 4. Calcular Crecimiento
        if (analytics.PreviousPeriodRevenue > 0)
            analytics.RevenueGrowth = (double)((analytics.TotalRevenue - analytics.PreviousPeriodRevenue) / analytics.PreviousPeriodRevenue) * 100;

        if (analytics.PreviousPeriodAds > 0)
            analytics.AdsGrowth = (double)(analytics.TotalAds - analytics.PreviousPeriodAds) / analytics.PreviousPeriodAds * 100;

        // 5. Distribución de Pagos (Real)
        var paymentsSql = @"
        SELECT 
            p.PaymentMethod as Method, 
            SUM(p.Amount + p.Surcharge) as Total,
            COUNT(p.Id) as Count
        FROM Payments p
        INNER JOIN Listings l ON p.ListingId = l.Id
        WHERE CAST(l.CreatedAt AS DATE) BETWEEN @Start AND @End
          AND l.Status = 'Published'
        GROUP BY p.PaymentMethod";

        analytics.PaymentsDistribution = (await conn.QueryAsync<PaymentMethodStat>(paymentsSql, new { Start = startDate.Date, End = endDate.Date })).ToList();

        // 6. Rendimiento por Categoría
        var categorySql = @"
        SELECT 
            c.Name as CategoryName, 
            SUM(l.AdFee) as Revenue,
            COUNT(l.Id) as AdsCount
        FROM Listings l
        JOIN Categories c ON l.CategoryId = c.Id
        WHERE CAST(l.CreatedAt AS DATE) BETWEEN @Start AND @End
          AND l.Status = 'Published'
        GROUP BY c.Name
        ORDER BY Revenue DESC";

        var catPerf = (await conn.QueryAsync<CategoryPerformanceStat>(categorySql, new { Start = startDate.Date, End = endDate.Date })).ToList();
        foreach (var cp in catPerf)
        {
            cp.Share = analytics.TotalRevenue > 0 ? (double)(cp.Revenue / analytics.TotalRevenue) * 100 : 0;
        }
        analytics.CategoryPerformance = catPerf;

        // 7. Análisis Horario (Peak Hours)
        var hourlySql = @"
        SELECT 
            DATEPART(HOUR, CreatedAt) as Hour,
            COUNT(Id) as Count
        FROM Listings
        WHERE CAST(CreatedAt AS DATE) BETWEEN @Start AND @End
          AND Status = 'Published'
        GROUP BY DATEPART(HOUR, CreatedAt)
        ORDER BY Hour";

        analytics.HourlyActivity = (await conn.QueryAsync<HourlyStat>(hourlySql, new { Start = startDate.Date, End = endDate.Date })).ToList();

        // 8. Tendencia Diaria
        var dailySql = @"
        SELECT 
            FORMAT(CreatedAt, 'dd/MM') as Day,
            SUM(AdFee) as Amount
        FROM Listings
        WHERE CAST(CreatedAt AS DATE) BETWEEN @Start AND @End
          AND Status = 'Published'
        GROUP BY FORMAT(CreatedAt, 'dd/MM'), CAST(CreatedAt AS DATE)
        ORDER BY CAST(CreatedAt AS DATE) ASC";

        analytics.DailyTrends = (await conn.QueryAsync<DailyRevenue>(dailySql, new { Start = startDate.Date, End = endDate.Date })).ToList();

        var sourceSql = @"
        SELECT 
            Origin, 
            COUNT(Id) as Count
        FROM Listings 
        WHERE CAST(CreatedAt AS DATE) BETWEEN @Start AND @End
          AND Status = 'Published'
        GROUP BY Origin";

        var sources = await conn.QueryAsync<dynamic>(sourceSql, new { Start = startDate.Date, End = endDate.Date });

        int total = 0;
        int web = 0;
        int mostrador = 0;

        foreach (var s in sources)
        {
            if (s.Origin == "Web") web = (int)s.Count;
            if (s.Origin == "Mostrador") mostrador = (int)s.Count;
            total += (int)s.Count;
        }

        analytics.SourceMix = new SourceMixDto
        {
            MostradorCount = mostrador,
            WebCount = web,
            MostradorPercent = total > 0 ? (mostrador * 100.0 / total) : 0,
            WebPercent = total > 0 ? (web * 100.0 / total) : 0
        };

        return analytics;
    }

    public async Task<CashierDashboardDto?> GetCashierStatsAsync(int userId, DateTime startDate, DateTime endDate)
    {
        using var conn = _connectionFactory.CreateConnection();

        // Filtramos tanto la recaudación como los pendientes por el rango seleccionado
        var sql = @"
        SELECT 
            (SELECT ISNULL(SUM(Amount + Surcharge), 0) FROM Payments p INNER JOIN Listings l ON p.ListingId = l.Id 
             WHERE l.UserId = @UserId AND CAST(l.CreatedAt AS DATE) BETWEEN @Start AND @End AND l.Status = 'Published') as MyRevenue,
            (SELECT COUNT(1) FROM Listings WHERE UserId = @UserId AND Status = 'Pending') as MyPendingAds,
            (SELECT COUNT(1) FROM Listings WHERE UserId = @UserId AND CAST(CreatedAt AS DATE) BETWEEN @Start AND @End AND Status = 'Published') as MyAdsCount";

        return await conn.QueryFirstOrDefaultAsync<CashierDashboardDto>(sql, new { UserId = userId, Start = startDate.Date, End = endDate.Date });
    }

    public async Task<GlobalReportDto> GetDetailedReportAsync(DateTime start, DateTime end, int? userId = null)
    {
        using var conn = _connectionFactory.CreateConnection();
        var report = new GlobalReportDto { FromDate = start, ToDate = end };

        var sql = @"
            SELECT 
                l.Id, l.CreatedAt as Date, l.Title, 
                c.Name as Category, u.Username as Cashier, l.AdFee as Amount,
                l.Origin as Source, cl.BillingName as ClientName
            FROM Listings l
            JOIN Categories c ON l.CategoryId = c.Id
            LEFT JOIN Users u ON l.UserId = u.Id
            LEFT JOIN Users cl ON l.ClientId = cl.Id
            WHERE CAST(l.CreatedAt AS DATE) BETWEEN @Start AND @End
              AND l.Status = 'Published'
              AND (@UserId IS NULL OR l.UserId = @UserId)
            ORDER BY l.CreatedAt ASC";

        var items = await conn.QueryAsync<ReportItemDto>(sql, new { Start = start.Date, End = end.Date, UserId = userId });
        report.Items = items.ToList();

        // TOTALES FÍSICOS (Solo lo que entró por caja)
        var totalsSql = @"
            SELECT 
                p.PaymentMethod, 
                SUM(p.Amount + p.Surcharge) as Total
            FROM Payments p
            INNER JOIN Listings l ON p.ListingId = l.Id
            WHERE CAST(l.CreatedAt AS DATE) BETWEEN @Start AND @End
              AND l.Status = 'Published'
              AND l.Origin = 'Mostrador'
              AND (@UserId IS NULL OR l.UserId = @UserId)
            GROUP BY p.PaymentMethod";

        var paymentTotals = await conn.QueryAsync<dynamic>(totalsSql, new { Start = start.Date, End = end.Date, UserId = userId });

        // Reiniciar totales para el reporte
        report.TotalCash = 0; report.TotalDebit = 0; report.TotalCredit = 0; report.TotalTransfer = 0;

        foreach (var p in paymentTotals)
        {
            string method = p.PaymentMethod;
            decimal total = (decimal)p.Total;
            switch (method)
            {
                case "Cash": report.TotalCash = total; break;
                case "Debit": report.TotalDebit = total; break;
                case "Credit": report.TotalCredit = total; break;
                case "Transfer": report.TotalTransfer = total; break;
            }
        }
        report.TotalRevenue = report.TotalCash + report.TotalDebit + report.TotalCredit + report.TotalTransfer;
        return report;
    }

    public async Task AddPaymentAsync(Payment payment)
    {
        using var conn = _connectionFactory.CreateConnection();
        var sql = @"
            INSERT INTO Payments (ListingId, Amount, PaymentMethod, CardPlan, Surcharge, PaymentDate, ExternalReference, ExternalId, Status)
            VALUES (@ListingId, @Amount, @PaymentMethod, @CardPlan, @Surcharge, @PaymentDate, @ExternalReference, @ExternalId, @Status)";

        await conn.ExecuteAsync(sql, payment);
    }

    public async Task<IEnumerable<dynamic>> GetActiveCashiersAsync()
    {
        using var conn = _connectionFactory.CreateConnection();
        // Traemos usuarios que tengan el rol adecuado para filtrar en el historial
        var sql = @"SELECT Id, Username FROM Users 
                WHERE Role IN ('Cajero', 'Admin') 
                AND IsActive = 1 
                ORDER BY Username ASC";

        return await conn.QueryAsync(sql);
    }

    public async Task UpdateOverlayStatusAsync(int id, int userId, string? status)
    {
        using var conn = _connectionFactory.CreateConnection();
        // Seguridad: Filtramos por Id del aviso Y Id del usuario dueño
        var sql = "UPDATE Listings SET OverlayStatus = @status WHERE Id = @id AND UserId = @userId";
        await conn.ExecuteAsync(sql, new { id, userId, status });
    }
}