using MediatR;
using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.App.Common.Geo;
using TakeoutSaaS.Application.App.Merchants.Commands;
using TakeoutSaaS.Application.App.Merchants.Dto;
using TakeoutSaaS.Domain.Merchants.Entities;
using TakeoutSaaS.Domain.Merchants.Enums;
using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Domain.Stores.Repositories;
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.Merchants.Handlers;

/// <summary>
/// 更新商户命令处理器。
/// </summary>
public sealed class UpdateMerchantCommandHandler(
    IMerchantRepository merchantRepository,
    IStoreRepository storeRepository,
    ITenantRepository tenantRepository,
    ITenantProvider tenantProvider,
    ICurrentUserAccessor currentUserAccessor,
    IAddressGeocodingService geocodingService,
    ILogger<UpdateMerchantCommandHandler> logger)
    : IRequestHandler<UpdateMerchantCommand, UpdateMerchantResultDto?>
{
    /// <inheritdoc />
    public async Task<UpdateMerchantResultDto?> Handle(UpdateMerchantCommand request, CancellationToken cancellationToken)
    {
        // 1. 获取操作者权限
        var currentTenantId = tenantProvider.GetCurrentTenantId();

        // 2. 读取商户信息
        var merchant = await merchantRepository.GetForUpdateAsync(request.MerchantId, currentTenantId, cancellationToken);

        if (merchant == null)
        {
            return null;
        }

        // 3. 规范化输入
        var name = NormalizeRequired(request.Name, "商户名称");
        var contactPhone = NormalizeRequired(request.ContactPhone, "联系电话");
        var licenseNumber = NormalizeOptional(request.LicenseNumber);
        var legalRepresentative = NormalizeOptional(request.LegalRepresentative);
        var registeredAddress = NormalizeOptional(request.RegisteredAddress);
        var contactEmail = NormalizeOptional(request.ContactEmail);
        var shouldReGeocode = !merchant.Latitude.HasValue ||
                              !merchant.Longitude.HasValue ||
                              !string.Equals(merchant.Address, registeredAddress, StringComparison.Ordinal);

        var now = DateTime.UtcNow;
        var actorId = currentUserAccessor.UserId == 0 ? (long?)null : currentUserAccessor.UserId;
        var actorName = ResolveActorName();
        var changes = new List<MerchantChangeLog>();
        var criticalChanged = false;

        TrackChange("name", merchant.BrandName, name, isCritical: true);
        TrackChange("licenseNumber", merchant.BusinessLicenseNumber, licenseNumber, isCritical: true);
        TrackChange("legalRepresentative", merchant.LegalPerson, legalRepresentative, isCritical: true);
        TrackChange("registeredAddress", merchant.Address, registeredAddress, isCritical: true);
        TrackChange("contactPhone", merchant.ContactPhone, contactPhone, isCritical: false);
        TrackChange("contactEmail", merchant.ContactEmail, contactEmail, isCritical: false);

        // 4. 写入字段
        merchant.BrandName = name;
        merchant.BusinessLicenseNumber = licenseNumber;
        merchant.LegalPerson = legalRepresentative;
        merchant.Address = registeredAddress;
        merchant.ContactPhone = contactPhone;
        merchant.ContactEmail = contactEmail;
        if (shouldReGeocode)
        {
            await TryGeocodeMerchantAsync(merchant, now, cancellationToken);
        }

        var requiresReview = merchant.Status == MerchantStatus.Approved && criticalChanged;
        if (requiresReview)
        {
            merchant.Status = MerchantStatus.Pending;
            merchant.IsFrozen = true;
            merchant.FrozenReason = "关键信息变更待审核";
            merchant.FrozenAt = now;
        }
        else if (merchant.Status == MerchantStatus.Rejected)
        {
            merchant.Status = MerchantStatus.Pending;
            merchant.IsFrozen = false;
            merchant.FrozenReason = null;
            merchant.FrozenAt = null;
        }

        // 5. 持久化日志与数据
        await merchantRepository.UpdateMerchantAsync(merchant, cancellationToken);
        foreach (var log in changes)
        {
            await merchantRepository.AddChangeLogAsync(log, cancellationToken);
        }

        if (requiresReview)
        {
            await merchantRepository.AddAuditLogAsync(new MerchantAuditLog
            {
                TenantId = merchant.TenantId,
                MerchantId = merchant.Id,
                Action = MerchantAuditAction.ReviewPendingReApproval,
                Title = "关键信息变更待审核",
                Description = "关键信息修改后已进入待审核状态",
                OperatorId = actorId,
                OperatorName = actorName
            }, cancellationToken);
        }

        try
        {
            await merchantRepository.SaveChangesAsync(cancellationToken);
        }
        catch (Exception exception) when (IsConcurrencyException(exception))
        {
            throw new BusinessException(ErrorCodes.Conflict, "商户信息已被修改，请刷新后重试");
        }

        logger.LogInformation("更新商户 {MerchantId} - {Name}", merchant.Id, merchant.BrandName);

        // 6. 返回更新结果
        var stores = await storeRepository.GetByMerchantIdAsync(merchant.Id, merchant.TenantId, cancellationToken);
        var tenant = await tenantRepository.FindByIdAsync(merchant.TenantId, cancellationToken);
        var detail = MerchantMapping.ToDetailDto(merchant, tenant?.Name, MerchantMapping.ToStoreDtos(stores));

        return new UpdateMerchantResultDto
        {
            Merchant = detail,
            RequiresReview = requiresReview
        };

        void TrackChange(string fieldName, string? oldValue, string? newValue, bool isCritical)
        {
            var normalizedOld = NormalizeOptional(oldValue);
            var normalizedNew = NormalizeOptional(newValue);
            if (string.Equals(normalizedOld, normalizedNew, StringComparison.Ordinal))
            {
                return;
            }

            if (isCritical)
            {
                criticalChanged = true;
            }

            changes.Add(new MerchantChangeLog
            {
                TenantId = merchant.TenantId,
                MerchantId = merchant.Id,
                FieldName = fieldName,
                OldValue = normalizedOld,
                NewValue = normalizedNew,
                ChangedBy = actorId,
                ChangedByName = actorName,
                ChangeType = "Update"
            });
        }
    }

    private async Task TryGeocodeMerchantAsync(Merchant merchant, DateTime now, CancellationToken cancellationToken)
    {
        var candidates = GeoAddressBuilder.BuildMerchantCandidates(merchant);
        if (candidates.Count == 0)
        {
            GeoLocationStateHelper.MarkPending(merchant, "缺少地址信息，等待补全后自动定位", now);
            return;
        }

        string? lastError = null;
        foreach (var candidate in candidates)
        {
            var result = await geocodingService.GeocodeAsync(candidate, cancellationToken);
            if (result.Succeeded && result.Latitude is not null && result.Longitude is not null)
            {
                GeoLocationStateHelper.MarkSuccess(
                    merchant,
                    (double)result.Latitude.Value,
                    (double)result.Longitude.Value,
                    now);
                return;
            }

            lastError = result.Message;
        }

        GeoLocationStateHelper.MarkPending(merchant, lastError ?? "地址地理编码失败，等待自动重试", now);
    }

    private static string NormalizeRequired(string? value, string fieldName)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new BusinessException(ErrorCodes.ValidationFailed, $"{fieldName}不能为空");
        }

        return value.Trim();
    }

    private static string? NormalizeOptional(string? value)
        => string.IsNullOrWhiteSpace(value) ? null : value.Trim();

    private string ResolveActorName()
        => currentUserAccessor.IsAuthenticated ? $"user:{currentUserAccessor.UserId}" : "system";

    private static bool IsConcurrencyException(Exception exception)
        => string.Equals(exception.GetType().Name, "DbUpdateConcurrencyException", StringComparison.Ordinal);
}
