using MediatR;
using TakeoutSaaS.Application.App.Finance.Invoice.Commands;
using TakeoutSaaS.Application.App.Finance.Invoice.Dto;
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.Security;
using TakeoutSaaS.Shared.Abstractions.Tenancy;

namespace TakeoutSaaS.Application.App.Finance.Invoice.Handlers;

/// <summary>
/// 申请发票处理器。
/// </summary>
public sealed class ApplyFinanceInvoiceRecordCommandHandler(
    ITenantInvoiceRepository repository,
    ITenantProvider tenantProvider,
    ICurrentUserAccessor currentUserAccessor)
    : IRequestHandler<ApplyFinanceInvoiceRecordCommand, FinanceInvoiceRecordDetailDto>
{
    /// <inheritdoc />
    public async Task<FinanceInvoiceRecordDetailDto> Handle(
        ApplyFinanceInvoiceRecordCommand request,
        CancellationToken cancellationToken)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();
        var invoiceType = FinanceInvoiceMapping.ParseInvoiceTypeRequired(request.InvoiceType);
        var applicantName = FinanceInvoiceMapping.NormalizeApplicantName(request.ApplicantName);
        var companyName = FinanceInvoiceMapping.NormalizeCompanyName(request.CompanyName);
        var taxpayerNumber = FinanceInvoiceMapping.NormalizeOptionalTaxpayerNumber(request.TaxpayerNumber);
        var amount = FinanceInvoiceMapping.NormalizeAmount(request.Amount);
        var orderNo = FinanceInvoiceMapping.NormalizeOrderNo(request.OrderNo);
        var contactEmail = FinanceInvoiceMapping.NormalizeOptionalEmail(request.ContactEmail);
        var contactPhone = FinanceInvoiceMapping.NormalizeOptionalPhone(request.ContactPhone);
        var applyRemark = FinanceInvoiceMapping.NormalizeOptionalRemark(request.ApplyRemark, "applyRemark");
        var appliedAt = request.AppliedAt.HasValue
            ? FinanceInvoiceMapping.NormalizeUtc(request.AppliedAt.Value)
            : DateTime.UtcNow;

        if (invoiceType == TenantInvoiceType.Special && string.IsNullOrWhiteSpace(taxpayerNumber))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "专票必须填写纳税人识别号");
        }

        var setting = await repository.GetSettingAsync(tenantId, cancellationToken)
                      ?? throw new BusinessException(ErrorCodes.BadRequest, "请先完成发票设置");
        EnsureTypeEnabled(setting, invoiceType);

        var invoiceNo = await GenerateInvoiceNoAsync(tenantId, cancellationToken);
        var entity = FinanceInvoiceDtoFactory.CreateRecordEntity(
            tenantId,
            invoiceNo,
            applicantName,
            companyName,
            taxpayerNumber,
            invoiceType,
            amount,
            orderNo,
            contactEmail,
            contactPhone,
            applyRemark,
            appliedAt);

        if (setting.EnableAutoIssue && amount <= setting.AutoIssueMaxAmount)
        {
            entity.Status = TenantInvoiceStatus.Issued;
            entity.IssuedAt = DateTime.UtcNow;
            entity.IssuedByUserId = currentUserAccessor.IsAuthenticated ? currentUserAccessor.UserId : null;
            entity.IssueRemark = "系统自动开票";
        }

        await repository.AddRecordAsync(entity, cancellationToken);
        await repository.SaveChangesAsync(cancellationToken);

        return FinanceInvoiceDtoFactory.ToRecordDetailDto(entity);
    }

    private static void EnsureTypeEnabled(TenantInvoiceSetting setting, TenantInvoiceType type)
    {
        if (type == TenantInvoiceType.Normal && !setting.EnableElectronicNormalInvoice)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "电子普通发票未启用");
        }

        if (type == TenantInvoiceType.Special && !setting.EnableElectronicSpecialInvoice)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "电子专用发票未启用");
        }
    }

    private async Task<string> GenerateInvoiceNoAsync(long tenantId, CancellationToken cancellationToken)
    {
        for (var index = 0; index < 10; index += 1)
        {
            var invoiceNo = FinanceInvoiceMapping.BuildInvoiceNo(DateTime.UtcNow);
            var exists = await repository.ExistsInvoiceNoAsync(tenantId, invoiceNo, cancellationToken);
            if (!exists)
            {
                return invoiceNo;
            }
        }

        throw new BusinessException(ErrorCodes.BadRequest, "生成发票号码失败，请稍后重试");
    }
}
