using MediatR;
using TakeoutSaaS.Application.App.Stores.Dto;
using TakeoutSaaS.Application.App.Stores.Queries;
using TakeoutSaaS.Application.App.Stores.Services;
using TakeoutSaaS.Domain.Stores.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;

namespace TakeoutSaaS.Application.App.Stores.Handlers;

/// <summary>
/// 配送范围检测查询处理器。
/// </summary>
public sealed class CheckStoreDeliveryZoneQueryHandler(
    IStoreRepository storeRepository,
    ITenantProvider tenantProvider,
    IDeliveryZoneService deliveryZoneService)
    : IRequestHandler<CheckStoreDeliveryZoneQuery, StoreDeliveryCheckResultDto>
{
    /// <inheritdoc />
    public async Task<StoreDeliveryCheckResultDto> Handle(CheckStoreDeliveryZoneQuery request, CancellationToken cancellationToken)
    {
        // 1. 校验门店存在
        var tenantId = tenantProvider.GetCurrentTenantId();
        var store = await storeRepository.FindByIdAsync(request.StoreId, tenantId, cancellationToken);
        if (store is null)
        {
            throw new BusinessException(ErrorCodes.NotFound, "门店不存在");
        }

        // 2. (空行后) 执行配送范围判断
        var zones = await storeRepository.GetDeliveryZonesAsync(request.StoreId, tenantId, cancellationToken);
        var result = deliveryZoneService.CheckPointInZones(zones, request.Longitude, request.Latitude);

        // 3. (空行后) 计算距离
        if (store.Longitude.HasValue && store.Latitude.HasValue)
        {
            var distance = CalculateDistanceKm(store.Latitude.Value, store.Longitude.Value, request.Latitude, request.Longitude);
            result = result with { Distance = (decimal)Math.Round(distance, 2, MidpointRounding.AwayFromZero) };
        }
        return result;
    }

    private static double CalculateDistanceKm(double latitude1, double longitude1, double latitude2, double longitude2)
    {
        const double earthRadius = 6371000d;
        var latRad1 = DegreesToRadians(latitude1);
        var latRad2 = DegreesToRadians(latitude2);
        var deltaLat = DegreesToRadians(latitude2 - latitude1);
        var deltaLon = DegreesToRadians(longitude2 - longitude1);
        var sinLat = Math.Sin(deltaLat / 2);
        var sinLon = Math.Sin(deltaLon / 2);
        var a = sinLat * sinLat + Math.Cos(latRad1) * Math.Cos(latRad2) * sinLon * sinLon;
        var c = 2 * Math.Asin(Math.Min(1, Math.Sqrt(a)));
        return earthRadius * c / 1000d;
    }

    private static double DegreesToRadians(double degrees) => degrees * (Math.PI / 180d);
}
