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.Repositories;

/// <summary>
/// 租户发票仓储 EF Core 实现。
/// </summary>
public sealed class EfTenantInvoiceRepository(TakeoutAppDbContext context) : ITenantInvoiceRepository
{
    /// <inheritdoc />
    public Task<TenantInvoiceSetting?> GetSettingAsync(long tenantId, CancellationToken cancellationToken = default)
    {
        return context.TenantInvoiceSettings
            .Where(item => item.TenantId == tenantId)
            .FirstOrDefaultAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task AddSettingAsync(TenantInvoiceSetting entity, CancellationToken cancellationToken = default)
    {
        return context.TenantInvoiceSettings.AddAsync(entity, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task UpdateSettingAsync(TenantInvoiceSetting entity, CancellationToken cancellationToken = default)
    {
        context.TenantInvoiceSettings.Update(entity);
        return Task.CompletedTask;
    }

    /// <inheritdoc />
    public async Task<(IReadOnlyList<TenantInvoiceRecord> Items, int TotalCount)> SearchRecordsAsync(
        long tenantId,
        DateTime? startUtc,
        DateTime? endUtc,
        TenantInvoiceStatus? status,
        TenantInvoiceType? invoiceType,
        string? keyword,
        int page,
        int pageSize,
        CancellationToken cancellationToken = default)
    {
        var normalizedPage = Math.Max(1, page);
        var normalizedPageSize = Math.Clamp(pageSize, 1, 500);

        var query = BuildRecordQuery(tenantId, startUtc, endUtc, status, invoiceType, keyword);

        var totalCount = await query.CountAsync(cancellationToken);
        if (totalCount == 0)
        {
            return ([], 0);
        }

        var items = await query
            .OrderByDescending(item => item.AppliedAt)
            .ThenByDescending(item => item.Id)
            .Skip((normalizedPage - 1) * normalizedPageSize)
            .Take(normalizedPageSize)
            .ToListAsync(cancellationToken);

        return (items, totalCount);
    }

    /// <inheritdoc />
    public async Task<TenantInvoiceRecordStatsSnapshot> GetStatsAsync(
        long tenantId,
        DateTime nowUtc,
        CancellationToken cancellationToken = default)
    {
        var utcNow = NormalizeUtc(nowUtc);
        var monthStart = new DateTime(utcNow.Year, utcNow.Month, 1, 0, 0, 0, DateTimeKind.Utc);

        var summary = await context.TenantInvoiceRecords
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId)
            .GroupBy(_ => 1)
            .Select(group => new
            {
                CurrentMonthIssuedAmount = group
                    .Where(item =>
                        item.Status == TenantInvoiceStatus.Issued &&
                        item.IssuedAt.HasValue &&
                        item.IssuedAt.Value >= monthStart &&
                        item.IssuedAt.Value <= utcNow)
                    .Sum(item => item.Amount),
                CurrentMonthIssuedCount = group
                    .Count(item =>
                        item.Status == TenantInvoiceStatus.Issued &&
                        item.IssuedAt.HasValue &&
                        item.IssuedAt.Value >= monthStart &&
                        item.IssuedAt.Value <= utcNow),
                PendingCount = group.Count(item => item.Status == TenantInvoiceStatus.Pending),
                VoidedCount = group.Count(item => item.Status == TenantInvoiceStatus.Voided)
            })
            .FirstOrDefaultAsync(cancellationToken);

        if (summary is null)
        {
            return new TenantInvoiceRecordStatsSnapshot();
        }

        return new TenantInvoiceRecordStatsSnapshot
        {
            CurrentMonthIssuedAmount = summary.CurrentMonthIssuedAmount,
            CurrentMonthIssuedCount = summary.CurrentMonthIssuedCount,
            PendingCount = summary.PendingCount,
            VoidedCount = summary.VoidedCount
        };
    }

    /// <inheritdoc />
    public Task<TenantInvoiceRecord?> FindRecordByIdAsync(
        long tenantId,
        long recordId,
        CancellationToken cancellationToken = default)
    {
        return context.TenantInvoiceRecords
            .Where(item => item.TenantId == tenantId && item.Id == recordId)
            .FirstOrDefaultAsync(cancellationToken);
    }

    /// <inheritdoc />
    public Task<bool> ExistsInvoiceNoAsync(
        long tenantId,
        string invoiceNo,
        CancellationToken cancellationToken = default)
    {
        return context.TenantInvoiceRecords
            .AsNoTracking()
            .AnyAsync(
                item => item.TenantId == tenantId && item.InvoiceNo == invoiceNo,
                cancellationToken);
    }

    /// <inheritdoc />
    public Task AddRecordAsync(TenantInvoiceRecord entity, CancellationToken cancellationToken = default)
    {
        return context.TenantInvoiceRecords.AddAsync(entity, cancellationToken).AsTask();
    }

    /// <inheritdoc />
    public Task UpdateRecordAsync(TenantInvoiceRecord entity, CancellationToken cancellationToken = default)
    {
        context.TenantInvoiceRecords.Update(entity);
        return Task.CompletedTask;
    }

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

    private IQueryable<TenantInvoiceRecord> BuildRecordQuery(
        long tenantId,
        DateTime? startUtc,
        DateTime? endUtc,
        TenantInvoiceStatus? status,
        TenantInvoiceType? invoiceType,
        string? keyword)
    {
        var query = context.TenantInvoiceRecords
            .AsNoTracking()
            .Where(item => item.TenantId == tenantId);

        if (startUtc.HasValue)
        {
            var normalizedStart = NormalizeUtc(startUtc.Value);
            query = query.Where(item => item.AppliedAt >= normalizedStart);
        }

        if (endUtc.HasValue)
        {
            var normalizedEnd = NormalizeUtc(endUtc.Value);
            query = query.Where(item => item.AppliedAt <= normalizedEnd);
        }

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

        if (invoiceType.HasValue)
        {
            query = query.Where(item => item.InvoiceType == invoiceType.Value);
        }

        var normalizedKeyword = (keyword ?? string.Empty).Trim();
        if (!string.IsNullOrWhiteSpace(normalizedKeyword))
        {
            var like = $"%{normalizedKeyword}%";
            query = query.Where(item =>
                EF.Functions.ILike(item.InvoiceNo, like) ||
                EF.Functions.ILike(item.CompanyName, like) ||
                EF.Functions.ILike(item.ApplicantName, like) ||
                EF.Functions.ILike(item.OrderNo, like));
        }

        return query;
    }

    private static DateTime NormalizeUtc(DateTime value)
    {
        return value.Kind switch
        {
            DateTimeKind.Utc => value,
            DateTimeKind.Local => value.ToUniversalTime(),
            _ => DateTime.SpecifyKind(value, DateTimeKind.Utc)
        };
    }
}
