using MediatR;
using TakeoutSaaS.Application.App.Common.Geo;
using TakeoutSaaS.Application.App.Stores.Commands;
using TakeoutSaaS.Application.App.Stores.Enums;
using TakeoutSaaS.Application.App.Stores.Services;
using TakeoutSaaS.Domain.Merchants.Repositories;
using TakeoutSaaS.Domain.Stores.Entities;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;

namespace TakeoutSaaS.Application.App.Stores.Handlers;

/// <summary>
/// 租户端更新门店命令处理器。
/// </summary>
public sealed class UpdateStoreCommandHandler(
    StoreContextService storeContextService,
    IStoreRepository storeRepository,
    IMerchantRepository merchantRepository,
    IAddressGeocodingService geocodingService)
    : IRequestHandler<UpdateStoreCommand>
{
    /// <inheritdoc />
    public async Task Handle(UpdateStoreCommand request, CancellationToken cancellationToken)
    {
        // 1. 解析上下文
        var context = storeContextService.GetRequiredContext();

        // 2. 查询并校验门店归属
        var existing = await storeRepository.FindByIdAsync(request.Id, context.TenantId, cancellationToken)
            ?? throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
        if (existing.MerchantId != context.MerchantId)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "无权操作其他商户门店");
        }
        var now = DateTime.UtcNow;
        var normalizedAddress = request.Address.Trim();
        var shouldReGeocode = !existing.Latitude.HasValue ||
                              !existing.Longitude.HasValue ||
                              !string.Equals(existing.Address?.Trim(), normalizedAddress, StringComparison.Ordinal);

        // 3. 若传入编码且发生变化，则校验唯一性并更新编码。
        var normalizedCode = request.Code?.Trim();
        if (!string.IsNullOrWhiteSpace(normalizedCode) &&
            !string.Equals(existing.Code, normalizedCode, StringComparison.OrdinalIgnoreCase))
        {
            var existingStores = await storeRepository.SearchAsync(
                context.TenantId,
                context.MerchantId,
                status: null,
                auditStatus: null,
                businessStatus: null,
                ownershipType: null,
                keyword: null,
                includeDeleted: false,
                cancellationToken: cancellationToken);
            var hasDuplicateCode = existingStores.Any(store =>
                store.Id != existing.Id &&
                string.Equals(store.Code, normalizedCode, StringComparison.OrdinalIgnoreCase));
            if (hasDuplicateCode)
            {
                throw new BusinessException(ErrorCodes.Conflict, "门店编码已存在");
            }

            existing.Code = normalizedCode;
        }

        // 4. 更新门店字段
        existing.Name = request.Name.Trim();
        existing.Phone = request.ContactPhone.Trim();
        existing.ManagerName = request.ManagerName.Trim();
        existing.Address = normalizedAddress;
        existing.CoverImageUrl = request.CoverImage?.Trim();
        existing.SignboardImageUrl = request.CoverImage?.Trim();
        existing.BusinessStatus = request.BusinessStatus ?? existing.BusinessStatus;
        var serviceTypes = request.ServiceTypes?.Count > 0
            ? request.ServiceTypes
            : [ServiceType.Delivery];
        StoreListMapping.ApplyServiceTypes(existing, serviceTypes);
        if (shouldReGeocode)
        {
            await TryGeocodeStoreAsync(existing, context.TenantId, context.MerchantId, now, cancellationToken);
        }

        // 5. 保存修改
        await storeRepository.UpdateStoreAsync(existing, cancellationToken);
        await storeRepository.SaveChangesAsync(cancellationToken);
    }

    private async Task TryGeocodeStoreAsync(
        Store store,
        long tenantId,
        long merchantId,
        DateTime now,
        CancellationToken cancellationToken)
    {
        var merchant = await merchantRepository.FindByIdAsync(merchantId, tenantId, cancellationToken);
        var candidates = GeoAddressBuilder.BuildStoreCandidates(store, merchant);
        if (candidates.Count == 0)
        {
            GeoLocationStateHelper.MarkPending(store, "缺少地址信息，等待补全后自动定位", 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(
                    store,
                    (double)result.Latitude.Value,
                    (double)result.Longitude.Value,
                    now);
                return;
            }

            lastError = result.Message;
        }

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