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

namespace TakeoutSaaS.Infrastructure.App.Persistence.Repositories;

/// <summary>
/// 租户账单仓储实现（EF Core）。
/// </summary>
public sealed class TenantBillingRepository(TakeoutAppDbContext context, ITenantContextAccessor tenantContextAccessor) : ITenantBillingRepository
{
    private long GetCurrentTenantId()
        => tenantContextAccessor.Current?.TenantId ?? 0;

    private Task<List<long>> GetActiveTenantIdsAsync(CancellationToken cancellationToken)
        => context.Tenants
            .AsNoTracking()
            .Where(x => x.DeletedAt == null && x.Id > 0)
            .Select(x => x.Id)
            .ToListAsync(cancellationToken);

    /// <inheritdoc />
    public async Task<IReadOnlyList<TenantBillingStatement>> SearchAsync(
        long tenantId,
        TenantBillingStatus? status,
        DateTime? from,
        DateTime? to,
        CancellationToken cancellationToken = default)
    {
        // 1. 构建基础查询：在当前租户上下文内查询
        var query = context.TenantBillingStatements
            .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
            .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
            .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 GetCurrentTenantId() == 0
            ? GetByStatementNoCrossTenantAsync(normalized, cancellationToken)
            : context.TenantBillingStatements
                .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
            .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;
        var currentTenantId = GetCurrentTenantId();
        if (currentTenantId != 0)
        {
            // 2. (空行后) 当前租户：仅查询本租户逾期账单
            return await context.TenantBillingStatements
                .AsNoTracking()
                .Where(x => x.DeletedAt == null
                            && x.DueDate < now
                            && x.Status == TenantBillingStatus.Pending)
                .OrderBy(x => x.DueDate)
                .ToListAsync(cancellationToken);
        }

        // 2. (空行后) 系统上下文：逐租户查询逾期账单并合并
        var tenantIds = await GetActiveTenantIdsAsync(cancellationToken);
        if (tenantIds.Count == 0)
        {
            return Array.Empty<TenantBillingStatement>();
        }

        var results = new List<TenantBillingStatement>();
        foreach (var tenantId in tenantIds)
        {
            using (tenantContextAccessor.EnterTenantScope(tenantId, "billing"))
            {
                var items = await context.TenantBillingStatements
                    .AsNoTracking()
                    .Where(x => x.DeletedAt == null
                                && x.DueDate < now
                                && x.Status == TenantBillingStatus.Pending)
                    .ToListAsync(cancellationToken);

                results.AddRange(items);
            }
        }

        return results
            .OrderBy(x => x.DueDate)
            .ToList();
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<TenantBillingStatement>> GetBillingsDueSoonAsync(int daysAhead, CancellationToken cancellationToken = default)
    {
        // 1. 计算到期窗口
        var now = DateTime.UtcNow;
        var dueTo = now.AddDays(daysAhead);
        var currentTenantId = GetCurrentTenantId();
        if (currentTenantId != 0)
        {
            // 2. (空行后) 当前租户：仅查询本租户即将到期账单
            return await context.TenantBillingStatements
                .AsNoTracking()
                .Where(x => x.DeletedAt == null
                            && x.Status == TenantBillingStatus.Pending
                            && x.DueDate >= now
                            && x.DueDate <= dueTo)
                .OrderBy(x => x.DueDate)
                .ToListAsync(cancellationToken);
        }

        // 2. (空行后) 系统上下文：逐租户查询即将到期账单并合并
        var tenantIds = await GetActiveTenantIdsAsync(cancellationToken);
        if (tenantIds.Count == 0)
        {
            return Array.Empty<TenantBillingStatement>();
        }

        var results = new List<TenantBillingStatement>();
        foreach (var tenantId in tenantIds)
        {
            using (tenantContextAccessor.EnterTenantScope(tenantId, "billing"))
            {
                var items = await context.TenantBillingStatements
                    .AsNoTracking()
                    .Where(x => x.DeletedAt == null
                                && x.Status == TenantBillingStatus.Pending
                                && x.DueDate >= now
                                && x.DueDate <= dueTo)
                    .ToListAsync(cancellationToken);

                results.AddRange(items);
            }
        }

        return results
            .OrderBy(x => x.DueDate)
            .ToList();
    }

    /// <inheritdoc />
    public async Task<IReadOnlyList<TenantBillingStatement>> GetByTenantIdAsync(long tenantId, CancellationToken cancellationToken = default)
    {
        return await context.TenantBillingStatements
            .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. 系统上下文：逐租户查找匹配账单；租户上下文：仅返回本租户账单
        var ids = billingIds.Distinct().ToArray();
        var currentTenantId = GetCurrentTenantId();
        if (currentTenantId != 0)
        {
            return await context.TenantBillingStatements
                .AsNoTracking()
                .Where(x => x.DeletedAt == null && ids.Contains(x.Id))
                .OrderByDescending(x => x.PeriodStart)
                .ToListAsync(cancellationToken);
        }

        var tenantIds = await GetActiveTenantIdsAsync(cancellationToken);
        if (tenantIds.Count == 0)
        {
            return Array.Empty<TenantBillingStatement>();
        }

        var results = new List<TenantBillingStatement>();
        foreach (var tenantId in tenantIds)
        {
            using (tenantContextAccessor.EnterTenantScope(tenantId, "billing"))
            {
                var items = await context.TenantBillingStatements
                    .AsNoTracking()
                    .Where(x => x.DeletedAt == null && ids.Contains(x.Id))
                    .ToListAsync(cancellationToken);

                results.AddRange(items);
            }
        }

        return results
            .OrderByDescending(x => x.PeriodStart)
            .ToList();
    }

    /// <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)
    {
        var normalizedPageNumber = pageNumber <= 0 ? 1 : pageNumber;
        var normalizedPageSize = pageSize <= 0 ? 20 : pageSize;
        var skip = (normalizedPageNumber - 1) * normalizedPageSize;
        var takePerTenant = normalizedPageNumber * normalizedPageSize;

        var currentTenantId = GetCurrentTenantId();
        if (currentTenantId != 0)
        {
            // 1. 当前租户：仅查询本租户数据（tenantId 为空则默认当前租户）
            var effectiveTenantId = tenantId ?? currentTenantId;
            var query = BuildTenantQuery(effectiveTenantId, status, from, to, minAmount, maxAmount, keyword);
            var total = await query.CountAsync(cancellationToken);
            var items = await query
                .OrderByDescending(x => x.PeriodEnd)
                .Skip(skip)
                .Take(normalizedPageSize)
                .ToListAsync(cancellationToken);

            return (items, total);
        }

        // 2. (空行后) 系统上下文：可按指定 tenantId 查询；tenantId 为空则跨租户聚合分页
        if (tenantId.HasValue)
        {
            using (tenantContextAccessor.EnterTenantScope(tenantId.Value, "billing"))
            {
                var query = BuildTenantQuery(tenantId.Value, status, from, to, minAmount, maxAmount, keyword);
                var total = await query.CountAsync(cancellationToken);
                var items = await query
                    .OrderByDescending(x => x.PeriodEnd)
                    .Skip(skip)
                    .Take(normalizedPageSize)
                    .ToListAsync(cancellationToken);

                return (items, total);
            }
        }

        // 3. (空行后) 跨租户分页：逐租户取 Top(N) 后合并排序再分页
        var normalizedKeyword = string.IsNullOrWhiteSpace(keyword) ? null : keyword.Trim();
        var tenantIds = await GetActiveTenantIdsAsync(cancellationToken);
        if (tenantIds.Count == 0)
        {
            return ([], 0);
        }

        HashSet<long>? keywordMatchedTenantIds = null;
        if (!string.IsNullOrWhiteSpace(normalizedKeyword))
        {
            var matched = await context.Tenants
                .AsNoTracking()
                .Where(x => x.DeletedAt == null && EF.Functions.ILike(x.Name, $"%{normalizedKeyword}%"))
                .Select(x => x.Id)
                .ToListAsync(cancellationToken);

            keywordMatchedTenantIds = matched.Count == 0 ? null : matched.ToHashSet();
        }

        var totalCount = 0;
        var collected = new List<TenantBillingStatement>();
        foreach (var tid in tenantIds)
        {
            using (tenantContextAccessor.EnterTenantScope(tid, "billing"))
            {
                var tenantQuery = BuildTenantQuery(tid, status, from, to, minAmount, maxAmount, keyword: null);

                if (!string.IsNullOrWhiteSpace(normalizedKeyword))
                {
                    // 3.1 租户名命中时视为匹配全部账单，否则按账单号筛选
                    var tenantNameMatched = keywordMatchedTenantIds is not null && keywordMatchedTenantIds.Contains(tid);
                    if (!tenantNameMatched)
                    {
                        tenantQuery = tenantQuery.Where(x => EF.Functions.ILike(x.StatementNo, $"%{normalizedKeyword}%"));
                    }
                }

                totalCount += await tenantQuery.CountAsync(cancellationToken);

                var topItems = await tenantQuery
                    .OrderByDescending(x => x.PeriodEnd)
                    .Take(takePerTenant)
                    .ToListAsync(cancellationToken);

                collected.AddRange(topItems);
            }
        }

        var pageItems = collected
            .OrderByDescending(x => x.PeriodEnd)
            .Skip(skip)
            .Take(normalizedPageSize)
            .ToList();

        return (pageItems, totalCount);
    }

    /// <inheritdoc />
    public async Task<TenantBillingStatistics> GetStatisticsAsync(
        long? tenantId,
        DateTime startDate,
        DateTime endDate,
        string groupBy,
        CancellationToken cancellationToken = default)
    {
        // 1. 统一时间基准与分组方式
        var now = DateTime.UtcNow;
        var normalizedGroupBy = NormalizeGroupBy(groupBy);
        var currentTenantId = GetCurrentTenantId();

        // 2. (空行后) 构造待统计租户列表
        List<long> targetTenantIds;
        if (currentTenantId != 0)
        {
            targetTenantIds = [tenantId ?? currentTenantId];
        }
        else if (tenantId.HasValue)
        {
            targetTenantIds = [tenantId.Value];
        }
        else
        {
            targetTenantIds = await GetActiveTenantIdsAsync(cancellationToken);
        }

        if (targetTenantIds.Count == 0)
        {
            return new TenantBillingStatistics();
        }

        // 3. (空行后) 拉取统计字段（逐租户上下文执行）
        var rows = new List<BillingStatisticsRow>();
        foreach (var tid in targetTenantIds)
        {
            if (currentTenantId == 0)
            {
                using (tenantContextAccessor.EnterTenantScope(tid, "billing"))
                {
                    var tenantRows = await BuildStatisticsQuery(startDate, endDate)
                        .Select(x => new BillingStatisticsRow
                        {
                            PeriodStart = x.PeriodStart,
                            AmountDue = x.AmountDue,
                            DiscountAmount = x.DiscountAmount,
                            TaxAmount = x.TaxAmount,
                            AmountPaid = x.AmountPaid,
                            Status = x.Status,
                            DueDate = x.DueDate
                        })
                        .ToListAsync(cancellationToken);

                    rows.AddRange(tenantRows);
                }

                continue;
            }

            var tenantRowsDirect = await BuildStatisticsQuery(startDate, endDate)
                .Where(x => x.TenantId == tid)
                .Select(x => new BillingStatisticsRow
                {
                    PeriodStart = x.PeriodStart,
                    AmountDue = x.AmountDue,
                    DiscountAmount = x.DiscountAmount,
                    TaxAmount = x.TaxAmount,
                    AmountPaid = x.AmountPaid,
                    Status = x.Status,
                    DueDate = x.DueDate
                })
                .ToListAsync(cancellationToken);

            rows.AddRange(tenantRowsDirect);
        }

        // 4. (空行后) 汇总统计
        var totalAmount = rows.Sum(x => x.AmountDue - x.DiscountAmount + x.TaxAmount);
        var paidAmount = rows.Where(x => x.Status == TenantBillingStatus.Paid).Sum(x => x.AmountPaid);
        var unpaidAmount = rows.Sum(x => (x.AmountDue - x.DiscountAmount + x.TaxAmount) - x.AmountPaid);
        var overdueAmount = rows
            .Where(x => (x.Status == TenantBillingStatus.Pending || x.Status == TenantBillingStatus.Overdue) && x.DueDate < now)
            .Sum(x => (x.AmountDue - x.DiscountAmount + x.TaxAmount) - x.AmountPaid);

        var totalCount = rows.Count;
        var paidCount = rows.Count(x => x.Status == TenantBillingStatus.Paid);
        var unpaidCount = rows.Count(x => x.Status == TenantBillingStatus.Pending || x.Status == TenantBillingStatus.Overdue);
        var overdueCount = rows.Count(x => (x.Status == TenantBillingStatus.Pending || x.Status == TenantBillingStatus.Overdue) && x.DueDate < now);

        // 5. (空行后) 趋势统计
        var trend = rows
            .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 GetCurrentTenantId() == 0
            ? FindByIdCrossTenantAsync(billingId, cancellationToken)
            : context.TenantBillingStatements
                .AsNoTracking()
                .FirstOrDefaultAsync(x => x.DeletedAt == null && x.Id == billingId, cancellationToken);
    }

    private IQueryable<TenantBillingStatement> BuildTenantQuery(
        long tenantId,
        TenantBillingStatus? status,
        DateTime? from,
        DateTime? to,
        decimal? minAmount,
        decimal? maxAmount,
        string? keyword)
    {
        var query = context.TenantBillingStatements
            .AsNoTracking()
            .Where(x => x.DeletedAt == null && x.TenantId == tenantId);

        if (status.HasValue)
        {
            query = query.Where(x => x.Status == status.Value);
        }

        if (from.HasValue)
        {
            query = query.Where(x => x.PeriodStart >= from.Value);
        }

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

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

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

        if (!string.IsNullOrWhiteSpace(keyword))
        {
            var normalized = keyword.Trim();
            query = query.Where(x => EF.Functions.ILike(x.StatementNo, $"%{normalized}%"));
        }

        return query;
    }

    private IQueryable<TenantBillingStatement> BuildStatisticsQuery(DateTime startDate, DateTime endDate)
        => context.TenantBillingStatements
            .AsNoTracking()
            .Where(x => x.DeletedAt == null
                        && x.PeriodStart >= startDate
                        && x.PeriodEnd <= endDate);

    private async Task<TenantBillingStatement?> FindByIdCrossTenantAsync(long billingId, CancellationToken cancellationToken)
    {
        var tenantIds = await GetActiveTenantIdsAsync(cancellationToken);
        foreach (var tenantId in tenantIds)
        {
            using (tenantContextAccessor.EnterTenantScope(tenantId, "billing"))
            {
                var billing = await context.TenantBillingStatements
                    .AsNoTracking()
                    .FirstOrDefaultAsync(x => x.DeletedAt == null && x.Id == billingId, cancellationToken);
                if (billing != null)
                {
                    return billing;
                }
            }
        }

        return null;
    }

    private async Task<TenantBillingStatement?> GetByStatementNoCrossTenantAsync(string statementNo, CancellationToken cancellationToken)
    {
        var tenantIds = await GetActiveTenantIdsAsync(cancellationToken);
        foreach (var tenantId in tenantIds)
        {
            using (tenantContextAccessor.EnterTenantScope(tenantId, "billing"))
            {
                var billing = await context.TenantBillingStatements
                    .AsNoTracking()
                    .FirstOrDefaultAsync(x => x.DeletedAt == null && x.StatementNo == statementNo, cancellationToken);
                if (billing != null)
                {
                    return billing;
                }
            }
        }

        return null;
    }

    private sealed record BillingStatisticsRow
    {
        public required DateTime PeriodStart { get; init; }

        public required decimal AmountDue { get; init; }

        public required decimal DiscountAmount { get; init; }

        public required decimal TaxAmount { get; init; }

        public required decimal AmountPaid { get; init; }

        public required TenantBillingStatus Status { get; init; }

        public required DateTime DueDate { get; init; }
    }

    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);
    }
}
