using System.Text.Json;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Application.App.Stores.Services;
using TakeoutSaaS.Domain.Stores.Entities;
using TakeoutSaaS.Domain.Stores.Enums;
using TakeoutSaaS.Infrastructure.App.Persistence;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Results;
using TakeoutSaaS.Shared.Web.Api;
using TakeoutSaaS.TenantApi.Contracts.Store;
using TakeoutSaaS.TenantApi.Services;

namespace TakeoutSaaS.TenantApi.Controllers;

/// <summary>
/// 门店配送设置模块。
/// </summary>
[ApiVersion("1.0")]
[Authorize]
[Route("api/tenant/v{version:apiVersion}/store")]
public sealed class StoreDeliveryController(
    TakeoutAppDbContext dbContext,
    StoreContextService storeContextService,
    TencentMapGeocodingService tencentMapGeocodingService) : BaseApiController
{
    /// <summary>
    /// 获取门店配送设置。
    /// </summary>
    [HttpGet("delivery")]
    [ProducesResponseType(typeof(ApiResponse<StoreDeliverySettingsDto>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<StoreDeliverySettingsDto>> Get([FromQuery] string storeId, CancellationToken cancellationToken)
    {
        var parsedStoreId = StoreApiHelpers.ParseRequiredSnowflake(storeId, "storeId");
        var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
        await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, parsedStoreId, cancellationToken);

        var setting = await dbContext.StoreDeliverySettings
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StoreId == parsedStoreId, cancellationToken);
        var polygonZones = await dbContext.StoreDeliveryZones
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
            .OrderBy(x => x.Priority)
            .ThenBy(x => x.SortOrder)
            .ToListAsync(cancellationToken);

        var radiusTiers = ParseRadiusTiers(setting?.RadiusTiersJson);
        var isConfigured = setting is not null || polygonZones.Count > 0 || radiusTiers.Count > 0;
        return ApiResponse<StoreDeliverySettingsDto>.Ok(new StoreDeliverySettingsDto
        {
            StoreId = parsedStoreId.ToString(),
            IsConfigured = isConfigured,
            Mode = setting is null ? null : StoreApiHelpers.ToDeliveryModeText(setting.Mode),
            RadiusCenterLatitude = setting?.RadiusCenterLatitude,
            RadiusCenterLongitude = setting?.RadiusCenterLongitude,
            RadiusTiers = radiusTiers,
            PolygonZones = polygonZones.Select(MapPolygonZone).ToList(),
            GeneralSettings = setting is null
                ? null
                : new DeliveryGeneralSettingsDto
                {
                    EtaAdjustmentMinutes = setting.EtaAdjustmentMinutes,
                    FreeDeliveryThreshold = setting.FreeDeliveryThreshold,
                    HourlyCapacityLimit = setting.HourlyCapacityLimit,
                    MaxDeliveryDistance = setting.MaxDeliveryDistance
                }
        });
    }

    /// <summary>
    /// 地址地理编码（服务端签名）。
    /// </summary>
    [HttpGet("delivery/geocode")]
    [ProducesResponseType(typeof(ApiResponse<StoreDeliveryGeocodeDto>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<StoreDeliveryGeocodeDto>> Geocode(
        [FromQuery] string address,
        CancellationToken cancellationToken)
    {
        var normalizedAddress = address?.Trim() ?? string.Empty;
        if (string.IsNullOrWhiteSpace(normalizedAddress))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "address 不能为空");
        }

        var result = await tencentMapGeocodingService.GeocodeAsync(normalizedAddress, cancellationToken);
        return ApiResponse<StoreDeliveryGeocodeDto>.Ok(new StoreDeliveryGeocodeDto
        {
            Address = normalizedAddress,
            Latitude = result?.Latitude,
            Longitude = result?.Longitude
        });
    }

    /// <summary>
    /// 保存门店配送设置。
    /// </summary>
    [HttpPost("delivery/save")]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<object>> Save([FromBody] StoreDeliverySettingsDto request, CancellationToken cancellationToken)
    {
        var parsedStoreId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, "storeId");
        if (string.IsNullOrWhiteSpace(request.Mode))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "mode 不能为空");
        }

        if (request.GeneralSettings is null)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "generalSettings 不能为空");
        }

        var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
        await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, parsedStoreId, cancellationToken);

        var setting = await dbContext.StoreDeliverySettings
            .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StoreId == parsedStoreId, cancellationToken);
        if (setting is null)
        {
            setting = new StoreDeliverySetting
            {
                StoreId = parsedStoreId
            };
            await dbContext.StoreDeliverySettings.AddAsync(setting, cancellationToken);
        }

        setting.Mode = StoreApiHelpers.ToDeliveryMode(request.Mode);
        var (radiusCenterLatitude, radiusCenterLongitude) = NormalizeRadiusCenter(
            request.RadiusCenterLatitude,
            request.RadiusCenterLongitude);
        setting.EtaAdjustmentMinutes = Math.Clamp(request.GeneralSettings.EtaAdjustmentMinutes, 0, 240);
        setting.FreeDeliveryThreshold = request.GeneralSettings.FreeDeliveryThreshold;
        setting.HourlyCapacityLimit = Math.Clamp(request.GeneralSettings.HourlyCapacityLimit, 1, 9999);
        setting.MaxDeliveryDistance = Math.Max(0m, request.GeneralSettings.MaxDeliveryDistance);
        setting.RadiusCenterLatitude = radiusCenterLatitude;
        setting.RadiusCenterLongitude = radiusCenterLongitude;
        setting.RadiusTiersJson = JsonSerializer.Serialize(NormalizeRadiusTiers(request.RadiusTiers), StoreApiHelpers.JsonOptions);

        var existingZones = await dbContext.StoreDeliveryZones
            .Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
            .ToListAsync(cancellationToken);
        var existingZoneMap = existingZones.ToDictionary(x => x.Id);
        var retainedIds = new HashSet<long>();

        foreach (var zone in request.PolygonZones ?? [])
        {
            var zoneId = StoreApiHelpers.ParseSnowflakeOrNull(zone.Id);
            StoreDeliveryZone? entity = null;
            if (zoneId.HasValue && existingZoneMap.TryGetValue(zoneId.Value, out var existing))
            {
                entity = existing;
                retainedIds.Add(existing.Id);
            }

            if (entity is null)
            {
                entity = new StoreDeliveryZone
                {
                    StoreId = parsedStoreId
                };
                await dbContext.StoreDeliveryZones.AddAsync(entity, cancellationToken);
            }

            entity.ZoneName = zone.Name?.Trim() ?? string.Empty;
            entity.PolygonGeoJson = NormalizePolygonGeoJson(zone.PolygonGeoJson);
            entity.Color = string.IsNullOrWhiteSpace(zone.Color) ? "#1677ff" : zone.Color.Trim();
            entity.MinimumOrderAmount = Math.Max(0m, zone.MinOrderAmount);
            entity.DeliveryFee = Math.Max(0m, zone.DeliveryFee);
            entity.EstimatedMinutes = Math.Max(1, zone.EtaMinutes);
            entity.Priority = Math.Max(1, zone.Priority);
            entity.SortOrder = entity.Priority;
        }

        var toDelete = existingZones
            .Where(x => !retainedIds.Contains(x.Id))
            .ToList();
        dbContext.StoreDeliveryZones.RemoveRange(toDelete);

        await dbContext.SaveChangesAsync(cancellationToken);
        return ApiResponse<object>.Ok(null);
    }

    /// <summary>
    /// 复制门店配送设置。
    /// </summary>
    [HttpPost("delivery/copy")]
    [ProducesResponseType(typeof(ApiResponse<CopyStoreDeliverySettingsResult>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<CopyStoreDeliverySettingsResult>> Copy([FromBody] CopyStoreDeliverySettingsRequest request, CancellationToken cancellationToken)
    {
        var sourceStoreId = StoreApiHelpers.ParseRequiredSnowflake(request.SourceStoreId, "sourceStoreId");
        var targetStoreIds = StoreApiHelpers.ParseSnowflakeList(request.TargetStoreIds);
        var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
        await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, sourceStoreId, cancellationToken);

        var accessibleTargetIds = await StoreApiHelpers.FilterAccessibleStoreIdsAsync(
            dbContext,
            tenantId,
            merchantId,
            targetStoreIds,
            cancellationToken);
        accessibleTargetIds.Remove(sourceStoreId);

        if (accessibleTargetIds.Count == 0)
        {
            return ApiResponse<CopyStoreDeliverySettingsResult>.Ok(new CopyStoreDeliverySettingsResult
            {
                CopiedCount = 0
            });
        }

        var sourceSetting = await dbContext.StoreDeliverySettings
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StoreId == sourceStoreId, cancellationToken);
        var sourceZones = await dbContext.StoreDeliveryZones
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.StoreId == sourceStoreId)
            .ToListAsync(cancellationToken);
        if (sourceSetting is null)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "源门店未配置配送设置，无法复制");
        }

        var targetSettings = await dbContext.StoreDeliverySettings
            .Where(x => x.TenantId == tenantId && accessibleTargetIds.Contains(x.StoreId))
            .ToListAsync(cancellationToken);
        var targetSettingMap = targetSettings.ToDictionary(x => x.StoreId);

        foreach (var targetStoreId in accessibleTargetIds)
        {
            if (!targetSettingMap.TryGetValue(targetStoreId, out var targetSetting))
            {
                targetSetting = new StoreDeliverySetting
                {
                    StoreId = targetStoreId
                };
                await dbContext.StoreDeliverySettings.AddAsync(targetSetting, cancellationToken);
            }

            targetSetting.Mode = sourceSetting.Mode;
            targetSetting.EtaAdjustmentMinutes = sourceSetting.EtaAdjustmentMinutes;
            targetSetting.FreeDeliveryThreshold = sourceSetting.FreeDeliveryThreshold;
            targetSetting.HourlyCapacityLimit = sourceSetting.HourlyCapacityLimit;
            targetSetting.MaxDeliveryDistance = sourceSetting.MaxDeliveryDistance;
            targetSetting.RadiusCenterLatitude = sourceSetting.RadiusCenterLatitude;
            targetSetting.RadiusCenterLongitude = sourceSetting.RadiusCenterLongitude;
            targetSetting.RadiusTiersJson = sourceSetting.RadiusTiersJson;
        }

        var targetZones = await dbContext.StoreDeliveryZones
            .Where(x => x.TenantId == tenantId && accessibleTargetIds.Contains(x.StoreId))
            .ToListAsync(cancellationToken);
        dbContext.StoreDeliveryZones.RemoveRange(targetZones);

        var clonedZones = accessibleTargetIds
            .SelectMany(targetStoreId => sourceZones.Select(zone => new StoreDeliveryZone
            {
                StoreId = targetStoreId,
                ZoneName = zone.ZoneName,
                PolygonGeoJson = zone.PolygonGeoJson,
                MinimumOrderAmount = zone.MinimumOrderAmount,
                DeliveryFee = zone.DeliveryFee,
                EstimatedMinutes = zone.EstimatedMinutes,
                Color = zone.Color,
                Priority = zone.Priority,
                SortOrder = zone.SortOrder
            }))
            .ToList();
        if (clonedZones.Count > 0)
        {
            await dbContext.StoreDeliveryZones.AddRangeAsync(clonedZones, cancellationToken);
        }

        await dbContext.SaveChangesAsync(cancellationToken);

        return ApiResponse<CopyStoreDeliverySettingsResult>.Ok(new CopyStoreDeliverySettingsResult
        {
            CopiedCount = accessibleTargetIds.Count
        });
    }

    private static List<RadiusTierDto> ParseRadiusTiers(string? raw)
    {
        if (string.IsNullOrWhiteSpace(raw))
        {
            return [];
        }

        try
        {
            var parsed = JsonSerializer.Deserialize<List<RadiusTierDto>>(raw, StoreApiHelpers.JsonOptions);
            return NormalizeRadiusTiers(parsed);
        }
        catch
        {
            return [];
        }
    }

    private static List<RadiusTierDto> NormalizeRadiusTiers(IEnumerable<RadiusTierDto>? source)
    {
        return (source ?? [])
            .Select((tier, index) =>
            {
                var minDistance = Math.Max(0m, tier.MinDistance);
                var maxDistance = Math.Max(minDistance + 0.01m, tier.MaxDistance);
                return new RadiusTierDto
                {
                    Id = string.IsNullOrWhiteSpace(tier.Id) ? $"tier-{index + 1}" : tier.Id,
                    MinDistance = minDistance,
                    MaxDistance = maxDistance,
                    DeliveryFee = Math.Max(0m, tier.DeliveryFee),
                    EtaMinutes = Math.Max(1, tier.EtaMinutes),
                    MinOrderAmount = Math.Max(0m, tier.MinOrderAmount),
                    Color = string.IsNullOrWhiteSpace(tier.Color) ? "#1677ff" : tier.Color
                };
            })
            .OrderBy(x => x.MinDistance)
            .ToList();
    }

    private static PolygonZoneDto MapPolygonZone(StoreDeliveryZone source)
    {
        return new PolygonZoneDto
        {
            Id = source.Id.ToString(),
            Name = source.ZoneName,
            Color = source.Color ?? string.Empty,
            DeliveryFee = source.DeliveryFee ?? 0m,
            EtaMinutes = source.EstimatedMinutes ?? 0,
            MinOrderAmount = source.MinimumOrderAmount ?? 0m,
            Priority = source.Priority,
            PolygonGeoJson = source.PolygonGeoJson
        };
    }

    private static string NormalizePolygonGeoJson(string? polygonGeoJson)
    {
        if (string.IsNullOrWhiteSpace(polygonGeoJson))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "请先绘制配送区域");
        }

        try
        {
            using var document = JsonDocument.Parse(polygonGeoJson);
            var root = document.RootElement;

            if (root.ValueKind != JsonValueKind.Object)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "区域图形格式非法");
            }

            if (!root.TryGetProperty("type", out var typeElement) ||
                !string.Equals(typeElement.GetString(), "FeatureCollection", StringComparison.OrdinalIgnoreCase))
            {
                throw new BusinessException(ErrorCodes.BadRequest, "区域图形必须为 FeatureCollection");
            }

            if (!root.TryGetProperty("features", out var featuresElement) ||
                featuresElement.ValueKind != JsonValueKind.Array ||
                featuresElement.GetArrayLength() == 0)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "至少绘制一个配送区域");
            }

            var polygonCount = 0;
            foreach (var feature in featuresElement.EnumerateArray())
            {
                if (feature.ValueKind != JsonValueKind.Object ||
                    !feature.TryGetProperty("geometry", out var geometryElement) ||
                    geometryElement.ValueKind != JsonValueKind.Object)
                {
                    throw new BusinessException(ErrorCodes.BadRequest, "区域图形格式非法");
                }

                if (!geometryElement.TryGetProperty("type", out var geometryTypeElement) ||
                    !string.Equals(geometryTypeElement.GetString(), "Polygon", StringComparison.OrdinalIgnoreCase))
                {
                    throw new BusinessException(ErrorCodes.BadRequest, "仅支持 Polygon 多边形区域");
                }

                if (!geometryElement.TryGetProperty("coordinates", out var coordinatesElement) ||
                    coordinatesElement.ValueKind != JsonValueKind.Array ||
                    coordinatesElement.GetArrayLength() == 0)
                {
                    throw new BusinessException(ErrorCodes.BadRequest, "多边形坐标不能为空");
                }

                ValidatePolygonCoordinates(coordinatesElement);
                polygonCount++;
            }

            if (polygonCount == 0)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "至少绘制一个配送区域");
            }

            return JsonSerializer.Serialize(root, StoreApiHelpers.JsonOptions);
        }
        catch (JsonException)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "区域图形格式非法");
        }
    }

    private static void ValidatePolygonCoordinates(JsonElement coordinatesElement)
    {
        foreach (var ringElement in coordinatesElement.EnumerateArray())
        {
            if (ringElement.ValueKind != JsonValueKind.Array)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "多边形坐标格式非法");
            }

            double? firstLng = null;
            double? firstLat = null;
            double? lastLng = null;
            double? lastLat = null;
            var pointCount = 0;

            foreach (var pointElement in ringElement.EnumerateArray())
            {
                if (!TryReadLngLat(pointElement, out var lng, out var lat))
                {
                    throw new BusinessException(ErrorCodes.BadRequest, "多边形坐标格式非法");
                }

                if (lng is < -180 or > 180 || lat is < -90 or > 90)
                {
                    throw new BusinessException(ErrorCodes.BadRequest, "多边形坐标越界");
                }

                if (pointCount == 0)
                {
                    firstLng = lng;
                    firstLat = lat;
                }

                lastLng = lng;
                lastLat = lat;
                pointCount++;
            }

            if (pointCount < 4)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "多边形顶点数量不足");
            }

            if (firstLng is null || firstLat is null || lastLng is null || lastLat is null)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "多边形坐标格式非法");
            }

            if (Math.Abs(firstLng.Value - lastLng.Value) > 1e-8 || Math.Abs(firstLat.Value - lastLat.Value) > 1e-8)
            {
                throw new BusinessException(ErrorCodes.BadRequest, "多边形首尾坐标必须闭合");
            }
        }
    }

    private static bool TryReadLngLat(JsonElement pointElement, out double lng, out double lat)
    {
        lng = 0;
        lat = 0;
        if (pointElement.ValueKind != JsonValueKind.Array)
        {
            return false;
        }

        var coordinateEnumerator = pointElement.EnumerateArray();
        if (!coordinateEnumerator.MoveNext() ||
            coordinateEnumerator.Current.ValueKind != JsonValueKind.Number ||
            !coordinateEnumerator.Current.TryGetDouble(out lng))
        {
            return false;
        }

        if (!coordinateEnumerator.MoveNext() ||
            coordinateEnumerator.Current.ValueKind != JsonValueKind.Number ||
            !coordinateEnumerator.Current.TryGetDouble(out lat))
        {
            return false;
        }

        return true;
    }

    private static (decimal? Latitude, decimal? Longitude) NormalizeRadiusCenter(
        decimal? latitude,
        decimal? longitude)
    {
        if (latitude is null && longitude is null)
        {
            return (null, null);
        }

        if (latitude is null || longitude is null)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "半径配送中心点经纬度必须同时填写");
        }

        if (latitude < -90m || latitude > 90m)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "纬度必须在 -90 到 90 之间");
        }

        if (longitude < -180m || longitude > 180m)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "经度必须在 -180 到 180 之间");
        }

        return (decimal.Round(latitude.Value, 7), decimal.Round(longitude.Value, 7));
    }
}
