using MediatR;
using System.Security.Cryptography;
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.Enums;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Security;

namespace TakeoutSaaS.Application.App.Stores.Handlers;

/// <summary>
/// 租户端创建门店命令处理器。
/// </summary>
public sealed class CreateStoreCommandHandler(
    StoreContextService storeContextService,
    IStoreRepository storeRepository,
    IMerchantRepository merchantRepository,
    IAddressGeocodingService geocodingService,
    ICurrentUserAccessor currentUserAccessor)
    : IRequestHandler<CreateStoreCommand>
{
    /// <inheritdoc />
    public async Task Handle(CreateStoreCommand request, CancellationToken cancellationToken)
    {
        // 1. 解析上下文
        var context = storeContextService.GetRequiredContext();
        var now = DateTime.UtcNow;

        // 2. 生成唯一门店编码
        var existingStores = await storeRepository.SearchAsync(
            context.TenantId,
            context.MerchantId,
            status: null,
            auditStatus: null,
            businessStatus: null,
            ownershipType: null,
            keyword: null,
            includeDeleted: false,
            cancellationToken: cancellationToken);
        var generatedCode = GenerateUniqueStoreCode(existingStores);

        // 3. 组装门店实体
        var serviceTypes = request.ServiceTypes?.Count > 0
            ? request.ServiceTypes
            : [ServiceType.Delivery];
        var store = new Store
        {
            TenantId = context.TenantId,
            MerchantId = context.MerchantId,
            Code = generatedCode,
            Name = request.Name.Trim(),
            Phone = request.ContactPhone.Trim(),
            ManagerName = request.ManagerName.Trim(),
            Address = request.Address.Trim(),
            CoverImageUrl = request.CoverImage?.Trim(),
            SignboardImageUrl = request.CoverImage?.Trim(),
            OwnershipType = StoreOwnershipType.SameEntity,
            AuditStatus = StoreAuditStatus.Activated,
            BusinessStatus = StoreBusinessStatus.Resting,
            SubmittedAt = now,
            ActivatedAt = now,
            Status = StoreStatus.Operating
        };
        StoreListMapping.ApplyServiceTypes(store, serviceTypes);
        await TryGeocodeStoreAsync(store, context.TenantId, context.MerchantId, now, cancellationToken);

        // 4. 持久化门店并记录自动审核通过
        await storeRepository.AddStoreAsync(store, cancellationToken);
        await storeRepository.SaveChangesAsync(cancellationToken);

        await storeRepository.AddAuditRecordAsync(new StoreAuditRecord
        {
            StoreId = store.Id,
            Action = StoreAuditAction.Approve,
            PreviousStatus = StoreAuditStatus.Draft,
            NewStatus = StoreAuditStatus.Activated,
            OperatorId = ResolveOperatorId(currentUserAccessor),
            OperatorName = ResolveOperatorName(currentUserAccessor),
            Remarks = "系统自动审核通过"
        }, 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);
    }

    /// <summary>
    /// 生成当前租户商户内唯一的门店编码。
    /// </summary>
    private static string GenerateUniqueStoreCode(IReadOnlyList<Store> existingStores)
    {
        var existingCodeSet = existingStores
            .Select(store => store.Code.Trim())
            .Where(code => !string.IsNullOrWhiteSpace(code))
            .ToHashSet(StringComparer.OrdinalIgnoreCase);

        const int maxRetryCount = 20;
        for (var attempt = 0; attempt < maxRetryCount; attempt++)
        {
            var candidate = $"ST{DateTime.UtcNow:yyMMddHHmmss}{RandomNumberGenerator.GetInt32(100, 1000)}";
            if (existingCodeSet.Add(candidate))
            {
                return candidate;
            }
        }

        throw new BusinessException(ErrorCodes.Conflict, "门店编码生成失败，请稍后重试");
    }

    private static long? ResolveOperatorId(ICurrentUserAccessor currentUserAccessor)
    {
        var id = currentUserAccessor.UserId;
        return id == 0 ? null : id;
    }

    private static string ResolveOperatorName(ICurrentUserAccessor currentUserAccessor)
    {
        var id = currentUserAccessor.UserId;
        return id == 0 ? "system" : $"user:{id}";
    }
}
