using MediatR;
using TakeoutSaaS.Application.App.Personal.Dto;
using TakeoutSaaS.Application.App.Personal.Queries;
using TakeoutSaaS.Application.App.Personal.Services;
using TakeoutSaaS.Application.App.Personal.Validators;
using TakeoutSaaS.Domain.Tenants.Entities;
using TakeoutSaaS.Domain.Tenants.Enums;
using TakeoutSaaS.Domain.Tenants.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Results;

namespace TakeoutSaaS.Application.App.Personal.Handlers;

/// <summary>
/// 分页查询个人中心账单记录处理器。
/// </summary>
public sealed class SearchPersonalBillingStatementsQueryHandler(
    PersonalContextService personalContextService,
    PersonalDateRangeValidator personalDateRangeValidator,
    PersonalAuditService personalAuditService,
    ITenantBillingRepository tenantBillingRepository,
    ITenantVisibilityRoleRuleRepository visibilityRoleRuleRepository)
    : IRequestHandler<SearchPersonalBillingStatementsQuery, PagedResult<PersonalBillingStatementDto>>
{
    private static readonly string[] DefaultBillingVisibleRoles = ["tenant-owner", "tenant-admin"];

    /// <summary>
    /// 处理查询。
    /// </summary>
    /// <param name="request">查询请求。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>账单分页结果。</returns>
    public async Task<PagedResult<PersonalBillingStatementDto>> Handle(SearchPersonalBillingStatementsQuery request, CancellationToken cancellationToken)
    {
        try
        {
            // 1. 获取上下文并校验可见角色
            var context = personalContextService.GetRequiredContext();
            var rule = await visibilityRoleRuleRepository.FindByTenantIdAsync(context.TenantId, cancellationToken);
            var allowedRoles = rule?.BillingVisibleRoleCodes?.Length > 0
                ? rule.BillingVisibleRoleCodes
                : DefaultBillingVisibleRoles;
            if (!HasAnyRole(context.RoleCodes, allowedRoles))
            {
                throw new BusinessException(ErrorCodes.Forbidden, "当前角色无权访问账单数据");
            }

            // 2. 解析时间窗与分页参数
            var dateRange = personalDateRangeValidator.Resolve(request.From, request.To);
            var page = request.Page <= 0 ? 1 : request.Page;
            var pageSize = request.PageSize <= 0 ? 20 : Math.Min(request.PageSize, 50);

            // 3. 查询并分页
            var statements = await tenantBillingRepository.SearchAsync(context.TenantId, null, dateRange.From, dateRange.To, cancellationToken);
            var ordered = statements.OrderByDescending(static x => x.PeriodEnd).ToList();
            var items = ordered
                .Skip((page - 1) * pageSize)
                .Take(pageSize)
                .Select(MapBilling)
                .ToList();

            // 4. 记录审计并返回
            await personalAuditService.RecordSensitiveQueryAsync("billing-statements", true, "查询账单成功", cancellationToken);
            return new PagedResult<PersonalBillingStatementDto>(items, page, pageSize, ordered.Count);
        }
        catch (Exception ex)
        {
            // 5. 记录失败审计并继续抛出
            await personalAuditService.RecordSensitiveQueryAsync("billing-statements", false, ex.Message, cancellationToken);
            throw;
        }
    }

    private static PersonalBillingStatementDto MapBilling(TenantBillingStatement billing)
    {
        // 1. 映射账单状态
        var status = billing.Status switch
        {
            TenantBillingStatus.Pending when billing.AmountPaid > 0 => "partial_paid",
            _ => billing.Status.ToString().ToLowerInvariant()
        };

        // 2. 返回 DTO
        return new PersonalBillingStatementDto
        {
            StatementId = billing.Id,
            BillingPeriodStart = DateOnly.FromDateTime(billing.PeriodStart),
            BillingPeriodEnd = DateOnly.FromDateTime(billing.PeriodEnd),
            AmountDue = billing.AmountDue,
            AmountPaid = billing.AmountPaid,
            Status = status,
            DueAt = billing.DueDate
        };
    }

    private static bool HasAnyRole(IReadOnlyList<string> currentRoles, IReadOnlyList<string> allowedRoles)
    {
        // 1. 归一化当前角色
        var roleSet = currentRoles
            .Where(static x => !string.IsNullOrWhiteSpace(x))
            .Select(static x => x.Trim())
            .ToHashSet(StringComparer.OrdinalIgnoreCase);

        // 2. 命中任一允许角色即通过
        return allowedRoles.Any(roleSet.Contains);
    }
}
