using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Domain.Tenants.Repositories;
using TakeoutSaaS.Infrastructure.App.Persistence;

namespace TakeoutSaaS.Infrastructure.App.Persistence.Repositories;

/// <summary>
/// 租户账单仓储实现（EF Core）。
/// </summary>
public sealed class TenantBillingRepository(TakeoutAppDbContext context) : ITenantBillingRepository
{
    /// <inheritdoc />
    public async Task<IReadOnlyList<TenantBillingStatement>> SearchAsync(
        long tenantId,
        TenantBillingStatus? status,
        DateTime? from,
        DateTime? to,
        CancellationToken cancellationToken = default)
    {
        // 1. 构建基础查询：忽略全局过滤器，显式过滤租户与软删除
        var query = context.TenantBillingStatements
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(x => x.DeletedAt == null && x.TenantId == tenantId);

        // 2. (空行后) 按状态过滤
        if (status.HasValue)
        {
            query = query.Where(x => x.Status == status.Value);
        }

        // 3. (空行后) 按日期范围过滤（账单周期）
        if (from.HasValue)
        {
            query = query.Where(x => x.PeriodStart >= from.Value);
        }

        if (to.HasValue)
        {
            query = query.Where(x => x.PeriodEnd <= to.Value);
        }

        // 4. (空行后) 排序返回
        return await query
            .OrderByDescending(x => x.PeriodEnd)
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task<TenantBillingStatement?> FindByIdAsync(long tenantId, long billingId, CancellationToken cancellationToken = default)
    {
        return context.TenantBillingStatements
            .IgnoreQueryFilters()
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.DeletedAt == null && x.TenantId == tenantId && x.Id == billingId, cancellationToken);
    }

    /// <inheritdoc />
    public Task<TenantBillingStatement?> FindByStatementNoAsync(long tenantId, string statementNo, CancellationToken cancellationToken = default)
    {
        var normalized = statementNo.Trim();

        return context.TenantBillingStatements
            .IgnoreQueryFilters()
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.DeletedAt == null && x.TenantId == tenantId && x.StatementNo == normalized, cancellationToken);
    }

    /// <inheritdoc />
    public Task<TenantBillingStatement?> GetByStatementNoAsync(string statementNo, CancellationToken cancellationToken = default)
    {
        var normalized = statementNo.Trim();

        return context.TenantBillingStatements
            .IgnoreQueryFilters()
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.DeletedAt == null && x.StatementNo == normalized, cancellationToken);
    }

    /// <inheritdoc />
    public Task<bool> ExistsNotCancelledByPeriodStartAsync(
        long tenantId,
        DateTime periodStart,
        CancellationToken cancellationToken = default)
    {
        return context.TenantBillingStatements
            .IgnoreQueryFilters()
            .AsNoTracking()
            .AnyAsync(
                x => x.DeletedAt == null
                     && x.TenantId == tenantId
                     && x.PeriodStart == periodStart
                     && x.Status != TenantBillingStatus.Cancelled,
                cancellationToken);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<TenantBillingStatement>> GetOverdueBillingsAsync(CancellationToken cancellationToken = default)
    {
        // 1. 以当前 UTC 时间作为逾期判断基准
        var now = DateTime.UtcNow;

        // 2. (空行后) 查询逾期且仍处于待支付的账单（仅 Pending 才允许自动切换为 Overdue）
        return await context.TenantBillingStatements
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(x => x.DeletedAt == null
                        && x.DueDate < now
                        && x.Status == TenantBillingStatus.Pending)
            .OrderBy(x => x.DueDate)
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<TenantBillingStatement>> GetBillingsDueSoonAsync(int daysAhead, CancellationToken cancellationToken = default)
    {
        // 1. 计算到期窗口
        var now = DateTime.UtcNow;
        var dueTo = now.AddDays(daysAhead);

        // 2. (空行后) 仅查询待支付账单
        return await context.TenantBillingStatements
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(x => x.DeletedAt == null
                        && x.Status == TenantBillingStatus.Pending
                        && x.DueDate >= now
                        && x.DueDate <= dueTo)
            .OrderBy(x => x.DueDate)
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<TenantBillingStatement>> GetByTenantIdAsync(long tenantId, CancellationToken cancellationToken = default)
    {
        return await context.TenantBillingStatements
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(x => x.DeletedAt == null && x.TenantId == tenantId)
            .OrderByDescending(x => x.PeriodEnd)
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<TenantBillingStatement>> GetByIdsAsync(IReadOnlyCollection<long> billingIds, CancellationToken cancellationToken = default)
    {
        if (billingIds.Count == 0)
        {
            return Array.Empty<TenantBillingStatement>();
        }

        // 1. 忽略全局过滤器以支持管理员端跨租户导出/批量操作
        return await context.TenantBillingStatements
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(x => x.DeletedAt == null && billingIds.Contains(x.Id))
            .OrderByDescending(x => x.PeriodStart)
            .ToListAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task AddAsync(TenantBillingStatement bill, CancellationToken cancellationToken = default)
    {
        return context.TenantBillingStatements.AddAsync(bill, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task UpdateAsync(TenantBillingStatement bill, CancellationToken cancellationToken = default)
    {
        context.TenantBillingStatements.Update(bill);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public Task SaveChangesAsync(CancellationToken cancellationToken = default)
    {
        return context.SaveChangesAsync(cancellationToken);
    }

    /// <inheritdoc />
    public async Task<(IReadOnlyList<TenantBillingStatement> Items, int Total)> SearchPagedAsync(
        long? tenantId,
        TenantBillingStatus? status,
        DateTime? from,
        DateTime? to,
        decimal? minAmount,
        decimal? maxAmount,
        string? keyword,
        int pageNumber,
        int pageSize,
        CancellationToken cancellationToken = default)
    {
        // 1. 构建基础查询（管理员端跨租户查询，忽略过滤器）
        var query = context.TenantBillingStatements
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(x => x.DeletedAt == null);

        // 2. (空行后) 按租户过滤（可选）
        if (tenantId.HasValue)
        {
            query = query.Where(x => x.TenantId == tenantId.Value);
        }

        // 3. (空行后) 按状态过滤（可选）
        if (status.HasValue)
        {
            query = query.Where(x => x.Status == status.Value);
        }

        // 4. (空行后) 按日期范围过滤（账单周期）
        if (from.HasValue)
        {
            query = query.Where(x => x.PeriodStart >= from.Value);
        }

        if (to.HasValue)
        {
            query = query.Where(x => x.PeriodEnd <= to.Value);
        }

        // 5. (空行后) 按金额范围过滤（应付金额，包含边界）
        if (minAmount.HasValue)
        {
            query = query.Where(x => x.AmountDue >= minAmount.Value);
        }

        if (maxAmount.HasValue)
        {
            query = query.Where(x => x.AmountDue <= maxAmount.Value);
        }

        // 6. (空行后) 关键字过滤（账单号或租户名）
        if (!string.IsNullOrWhiteSpace(keyword))
        {
            var normalized = keyword.Trim();

            query =
                from b in query
                join t in context.Tenants
                        .IgnoreQueryFilters()
                        .AsNoTracking()
                        .Where(x => x.DeletedAt == null)
                    on b.TenantId equals t.Id
                where EF.Functions.ILike(b.StatementNo, $"%{normalized}%")
                      || EF.Functions.ILike(t.Name, $"%{normalized}%")
                select b;
        }

        // 7. (空行后) 统计总数
        var total = await query.CountAsync(cancellationToken);

        // 8. (空行后) 分页查询
        var items = await query
            .OrderByDescending(x => x.PeriodEnd)
            .Skip((pageNumber - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync(cancellationToken);

        return (items, total);
    }

    /// <inheritdoc />
    public async Task<TenantBillingStatistics> GetStatisticsAsync(
        long? tenantId,
        DateTime startDate,
        DateTime endDate,
        string groupBy,
        CancellationToken cancellationToken = default)
    {
        // 1. 构建基础查询（忽略过滤器，显式过滤软删除/租户/时间范围）
        var query = context.TenantBillingStatements
            .IgnoreQueryFilters()
            .AsNoTracking()
            .Where(x => x.DeletedAt == null
                        && (!tenantId.HasValue || x.TenantId == tenantId.Value)
                        && x.PeriodStart >= startDate
                        && x.PeriodEnd <= endDate);

        // 2. (空行后) 聚合统计（金额统一使用：应付 - 折扣 + 税费）
        var now = DateTime.UtcNow;
        var totalAmount = await query.SumAsync(x => x.AmountDue - x.DiscountAmount + x.TaxAmount, cancellationToken);
        var paidAmount = await query.Where(x => x.Status == TenantBillingStatus.Paid).SumAsync(x => x.AmountPaid, cancellationToken);
        var unpaidAmount = await query.SumAsync(x => (x.AmountDue - x.DiscountAmount + x.TaxAmount) - x.AmountPaid, cancellationToken);
        var overdueAmount = await query
            .Where(x => (x.Status == TenantBillingStatus.Pending || x.Status == TenantBillingStatus.Overdue) && x.DueDate < now)
            .SumAsync(x => (x.AmountDue - x.DiscountAmount + x.TaxAmount) - x.AmountPaid, cancellationToken);

        // 3. (空行后) 数量统计
        var totalCount = await query.CountAsync(cancellationToken);
        var paidCount = await query.CountAsync(x => x.Status == TenantBillingStatus.Paid, cancellationToken);
        var unpaidCount = await query.CountAsync(x => x.Status == TenantBillingStatus.Pending || x.Status == TenantBillingStatus.Overdue, cancellationToken);
        var overdueCount = await query.CountAsync(x => (x.Status == TenantBillingStatus.Pending || x.Status == TenantBillingStatus.Overdue) && x.DueDate < now, cancellationToken);

        // 4. (空行后) 趋势统计
        var normalizedGroupBy = NormalizeGroupBy(groupBy);
        var trendRaw = await query
            .Select(x => new
            {
                x.PeriodStart,
                x.AmountDue,
                x.DiscountAmount,
                x.TaxAmount,
                x.AmountPaid
            })
            .ToListAsync(cancellationToken);

        // 4.1 (空行后) 在内存中按 Day/Week/Month 聚合（避免依赖特定数据库函数扩展）
        var trend = trendRaw
            .GroupBy(x => GetTrendBucket(x.PeriodStart, normalizedGroupBy))
            .Select(g => new TenantBillingTrendDataPoint
            {
                Period = g.Key,
                Count = g.Count(),
                TotalAmount = g.Sum(x => x.AmountDue - x.DiscountAmount + x.TaxAmount),
                PaidAmount = g.Sum(x => x.AmountPaid)
            })
            .OrderBy(x => x.Period)
            .ToList();

        return new TenantBillingStatistics
        {
            TotalAmount = totalAmount,
            PaidAmount = paidAmount,
            UnpaidAmount = unpaidAmount,
            OverdueAmount = overdueAmount,
            TotalCount = totalCount,
            PaidCount = paidCount,
            UnpaidCount = unpaidCount,
            OverdueCount = overdueCount,
            TrendData = trend
        };
    }

    /// <inheritdoc />
    public Task<TenantBillingStatement?> FindByIdAsync(long billingId, CancellationToken cancellationToken = default)
    {
        return context.TenantBillingStatements
            .IgnoreQueryFilters()
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.DeletedAt == null && x.Id == billingId, cancellationToken);
    }

    private static string NormalizeGroupBy(string groupBy)
    {
        return groupBy.Trim() switch
        {
            "Week" => "Week",
            "Month" => "Month",
            _ => "Day"
        };
    }

    private static DateTime GetTrendBucket(DateTime periodStart, string groupBy)
    {
        var date = periodStart.Date;

        return groupBy switch
        {
            "Month" => new DateTime(date.Year, date.Month, 1, 0, 0, 0, DateTimeKind.Utc),
            "Week" => GetWeekStart(date),
            _ => new DateTime(date.Year, date.Month, date.Day, 0, 0, 0, DateTimeKind.Utc)
        };
    }

    private static DateTime GetWeekStart(DateTime date)
    {
        // 1. 将周一作为一周起始（与 PostgreSQL date_trunc('week', ...) 对齐）
        var dayOfWeek = (int)date.DayOfWeek; // Sunday=0, Monday=1, ...
        var daysSinceMonday = (dayOfWeek + 6) % 7;

        // 2. (空行后) 回退到周一 00:00:00（UTC）
        var monday = date.AddDays(-daysSinceMonday);
        return new DateTime(monday.Year, monday.Month, monday.Day, 0, 0, 0, DateTimeKind.Utc);
    }
}
