using System.Text.Json;
using System.Security.Cryptography;
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;

namespace TakeoutSaaS.TenantApi.Controllers;

/// <summary>
/// 门店自提设置模块。
/// </summary>
[ApiVersion("1.0")]
[Authorize]
[Route("api/tenant/v{version:apiVersion}/store")]
public sealed class StorePickupController(
    TakeoutAppDbContext dbContext,
    StoreContextService storeContextService) : BaseApiController
{
    /// <summary>
    /// 获取门店自提设置。
    /// </summary>
    [HttpGet("pickup")]
    [ProducesResponseType(typeof(ApiResponse<StorePickupSettingsDto>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<StorePickupSettingsDto>> 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.StorePickupSettings
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StoreId == parsedStoreId, cancellationToken);

        var slots = await dbContext.StorePickupSlots
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
            .OrderBy(x => x.StartTime)
            .ThenBy(x => x.Name)
            .ToListAsync(cancellationToken);

        var fineRule = ParseFineRule(setting?.FineRuleJson);
        var previewDays = fineRule is null ? [] : BuildPreviewDays(fineRule);
        var isConfigured = setting is not null || slots.Count > 0;

        var response = new StorePickupSettingsDto
        {
            StoreId = parsedStoreId.ToString(),
            IsConfigured = isConfigured,
            Mode = setting is null ? null : StoreApiHelpers.ToPickupModeText(setting.Mode),
            BasicSettings = setting is null
                ? null
                : new PickupBasicSettingsDto
                {
                    AllowSameDayPickup = setting.AllowToday,
                    BookingDays = setting.AllowDaysAhead,
                    MaxItemsPerOrder = setting.MaxQuantityPerOrder
                },
            BigSlots = slots.Select(slot => new PickupSlotDto
            {
                Id = slot.Id.ToString(),
                Name = slot.Name,
                StartTime = StoreApiHelpers.ToHHmm(slot.StartTime),
                EndTime = StoreApiHelpers.ToHHmm(slot.EndTime),
                CutoffMinutes = slot.CutoffMinutes,
                Capacity = slot.Capacity,
                ReservedCount = slot.ReservedCount,
                DayOfWeeks = StoreApiHelpers.DeserializeWeekdays(slot.Weekdays),
                Enabled = slot.IsEnabled
            }).ToList(),
            FineRule = fineRule,
            PreviewDays = previewDays
        };

        return ApiResponse<StorePickupSettingsDto>.Ok(response);
    }

    /// <summary>
    /// 保存自提基础设置。
    /// </summary>
    [HttpPost("pickup/basic/save")]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<object>> SaveBasic([FromBody] SavePickupBasicSettingsRequest request, CancellationToken cancellationToken)
    {
        var parsedStoreId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, "storeId");
        var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
        await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, parsedStoreId, cancellationToken);

        var setting = await EnsurePickupSettingAsync(tenantId, parsedStoreId, cancellationToken);
        setting.AllowToday = request.BasicSettings.AllowSameDayPickup;
        setting.AllowDaysAhead = Math.Clamp(request.BasicSettings.BookingDays, 1, 30);
        setting.MaxQuantityPerOrder = request.BasicSettings.MaxItemsPerOrder;
        setting.Mode = request.Mode is null ? setting.Mode : StoreApiHelpers.ToPickupMode(request.Mode);
        setting.RowVersion = CreateRowVersion();

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

    /// <summary>
    /// 保存自提大时段。
    /// </summary>
    [HttpPost("pickup/slots/save")]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<object>> SaveSlots([FromBody] SavePickupSlotsRequest request, CancellationToken cancellationToken)
    {
        var parsedStoreId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, "storeId");
        var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
        await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, parsedStoreId, cancellationToken);

        var setting = await EnsurePickupSettingAsync(tenantId, parsedStoreId, cancellationToken);
        setting.Mode = request.Mode is null ? setting.Mode : StoreApiHelpers.ToPickupMode(request.Mode);
        setting.RowVersion = CreateRowVersion();

        var existingSlots = await dbContext.StorePickupSlots
            .Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
            .ToListAsync(cancellationToken);
        dbContext.StorePickupSlots.RemoveRange(existingSlots);

        var toCreate = new List<StorePickupSlot>();
        foreach (var slot in request.Slots ?? [])
        {
            var startTime = StoreApiHelpers.ParseRequiredTime(slot.StartTime, "slot.startTime");
            var endTime = StoreApiHelpers.ParseRequiredTime(slot.EndTime, "slot.endTime");
            if (startTime >= endTime)
            {
                continue;
            }

            var capacity = Math.Max(0, slot.Capacity);
            toCreate.Add(new StorePickupSlot
            {
                StoreId = parsedStoreId,
                Name = string.IsNullOrWhiteSpace(slot.Name) ? "时段" : slot.Name.Trim(),
                StartTime = startTime,
                EndTime = endTime,
                CutoffMinutes = Math.Max(0, slot.CutoffMinutes),
                Capacity = capacity,
                ReservedCount = Math.Clamp(slot.ReservedCount, 0, capacity),
                Weekdays = StoreApiHelpers.SerializeWeekdays(slot.DayOfWeeks),
                IsEnabled = slot.Enabled,
                RowVersion = CreateRowVersion()
            });
        }

        if (toCreate.Count > 0)
        {
            await dbContext.StorePickupSlots.AddRangeAsync(toCreate, cancellationToken);
        }

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

    /// <summary>
    /// 保存自提精细规则。
    /// </summary>
    [HttpPost("pickup/fine-rule/save")]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<object>> SaveFineRule([FromBody] SavePickupFineRuleRequest request, CancellationToken cancellationToken)
    {
        var parsedStoreId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, "storeId");
        var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
        await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, parsedStoreId, cancellationToken);

        var setting = await EnsurePickupSettingAsync(tenantId, parsedStoreId, cancellationToken);
        setting.Mode = request.Mode is null ? setting.Mode : StoreApiHelpers.ToPickupMode(request.Mode);
        setting.RowVersion = CreateRowVersion();

        var normalizedRule = NormalizeFineRule(request.FineRule);
        setting.FineRuleJson = JsonSerializer.Serialize(normalizedRule, StoreApiHelpers.JsonOptions);

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

    /// <summary>
    /// 保存自提模式。
    /// </summary>
    [HttpPost("pickup/mode/save")]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<object>> SaveMode([FromBody] SavePickupModeRequest request, CancellationToken cancellationToken)
    {
        var parsedStoreId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, "storeId");
        var mode = ParseRequiredPickupMode(request.Mode);
        var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
        await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, parsedStoreId, cancellationToken);

        var setting = await EnsurePickupSettingAsync(tenantId, parsedStoreId, cancellationToken);
        setting.Mode = mode;
        setting.RowVersion = CreateRowVersion();

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

    /// <summary>
    /// 复制自提设置。
    /// </summary>
    [HttpPost("pickup/copy")]
    [ProducesResponseType(typeof(ApiResponse<CopyStorePickupSettingsResult>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<CopyStorePickupSettingsResult>> Copy([FromBody] CopyStorePickupSettingsRequest 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<CopyStorePickupSettingsResult>.Ok(new CopyStorePickupSettingsResult
            {
                CopiedCount = 0
            });
        }

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

        var targetSettings = await dbContext.StorePickupSettings
            .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 StorePickupSetting
                {
                    StoreId = targetStoreId,
                    RowVersion = CreateRowVersion()
                };
                await dbContext.StorePickupSettings.AddAsync(targetSetting, cancellationToken);
            }

            targetSetting.AllowToday = sourceSetting.AllowToday;
            targetSetting.AllowDaysAhead = sourceSetting.AllowDaysAhead;
            targetSetting.DefaultCutoffMinutes = sourceSetting.DefaultCutoffMinutes;
            targetSetting.MaxQuantityPerOrder = sourceSetting.MaxQuantityPerOrder;
            targetSetting.Mode = sourceSetting.Mode;
            targetSetting.FineRuleJson = sourceSetting.FineRuleJson;
            targetSetting.RowVersion = CreateRowVersion();
        }

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

        var clonedSlots = accessibleTargetIds
            .SelectMany(targetStoreId => sourceSlots.Select(slot => new StorePickupSlot
            {
                StoreId = targetStoreId,
                Name = slot.Name,
                StartTime = slot.StartTime,
                EndTime = slot.EndTime,
                CutoffMinutes = slot.CutoffMinutes,
                Capacity = slot.Capacity,
                ReservedCount = slot.ReservedCount,
                Weekdays = slot.Weekdays,
                IsEnabled = slot.IsEnabled,
                RowVersion = CreateRowVersion()
            }))
            .ToList();

        if (clonedSlots.Count > 0)
        {
            await dbContext.StorePickupSlots.AddRangeAsync(clonedSlots, cancellationToken);
        }

        await dbContext.SaveChangesAsync(cancellationToken);

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

    private async Task<StorePickupSetting> EnsurePickupSettingAsync(long tenantId, long storeId, CancellationToken cancellationToken)
    {
        var setting = await dbContext.StorePickupSettings
            .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StoreId == storeId, cancellationToken);
        if (setting is not null)
        {
            return setting;
        }

        setting = new StorePickupSetting
        {
            StoreId = storeId,
            RowVersion = CreateRowVersion()
        };
        await dbContext.StorePickupSettings.AddAsync(setting, cancellationToken);
        return setting;
    }

    private static byte[] CreateRowVersion()
    {
        return RandomNumberGenerator.GetBytes(16);
    }

    private static StorePickupMode ParseRequiredPickupMode(string? mode)
    {
        return (mode ?? string.Empty).Trim().ToLowerInvariant() switch
        {
            "big" => StorePickupMode.Big,
            "fine" => StorePickupMode.Fine,
            _ => throw new BusinessException(ErrorCodes.BadRequest, "mode 非法")
        };
    }

    private static PickupFineRuleDto? ParseFineRule(string? raw)
    {
        if (string.IsNullOrWhiteSpace(raw))
        {
            return null;
        }

        try
        {
            var parsed = JsonSerializer.Deserialize<PickupFineRuleDto>(raw, StoreApiHelpers.JsonOptions);
            return parsed is null ? null : NormalizeFineRule(parsed);
        }
        catch
        {
            return null;
        }
    }

    private static PickupFineRuleDto NormalizeFineRule(PickupFineRuleDto source)
    {
        var start = StoreApiHelpers.ParseRequiredTime(source.DayStartTime, "fineRule.dayStartTime");
        var end = StoreApiHelpers.ParseRequiredTime(source.DayEndTime, "fineRule.dayEndTime");

        return new PickupFineRuleDto
        {
            IntervalMinutes = Math.Clamp(source.IntervalMinutes, 5, 180),
            SlotCapacity = Math.Clamp(source.SlotCapacity, 1, 999),
            DayStartTime = StoreApiHelpers.ToHHmm(start),
            DayEndTime = StoreApiHelpers.ToHHmm(end),
            MinAdvanceHours = Math.Clamp(source.MinAdvanceHours, 0, 72),
            DayOfWeeks = (source.DayOfWeeks ?? [])
                .Distinct()
                .Where(x => x is >= 0 and <= 6)
                .OrderBy(x => x)
                .ToList()
        };
    }

    private static List<PickupPreviewDayDto> BuildPreviewDays(PickupFineRuleDto fineRule)
    {
        var now = DateTime.Now;
        var startMinutes = StoreApiHelpers.ParseRequiredTime(fineRule.DayStartTime, "fineRule.dayStartTime").Hours * 60
                           + StoreApiHelpers.ParseRequiredTime(fineRule.DayStartTime, "fineRule.dayStartTime").Minutes;
        var endMinutes = StoreApiHelpers.ParseRequiredTime(fineRule.DayEndTime, "fineRule.dayEndTime").Hours * 60
                         + StoreApiHelpers.ParseRequiredTime(fineRule.DayEndTime, "fineRule.dayEndTime").Minutes;
        if (fineRule.IntervalMinutes <= 0 || endMinutes <= startMinutes)
        {
            return [];
        }

        var results = new List<PickupPreviewDayDto>();
        for (var offset = 0; offset < 3; offset++)
        {
            var date = now.Date.AddDays(offset);
            var uiDayOfWeek = StoreApiHelpers.DotNetDayOfWeekToUi(date.DayOfWeek);
            var enabled = fineRule.DayOfWeeks.Contains(uiDayOfWeek);
            var dateText = StoreApiHelpers.ToDateOnly(date);

            var subLabel = $"{GetWeekdayLabel(uiDayOfWeek)} {(offset == 0 ? "今天" : offset == 1 ? "明天" : "后天")}";
            var slots = enabled
                ? BuildPreviewSlots(date, startMinutes, endMinutes, fineRule.IntervalMinutes, fineRule.SlotCapacity, fineRule.MinAdvanceHours)
                : [];

            results.Add(new PickupPreviewDayDto
            {
                Date = dateText,
                Label = $"{date.Month}/{date.Day}",
                SubLabel = subLabel,
                Slots = slots
            });
        }

        return results;
    }

    private static List<PickupPreviewSlotDto> BuildPreviewSlots(
        DateTime date,
        int startMinutes,
        int endMinutes,
        int intervalMinutes,
        int slotCapacity,
        int minAdvanceHours)
    {
        var now = DateTime.Now;
        var normalizedCapacity = Math.Max(0, slotCapacity);
        var results = new List<PickupPreviewSlotDto>();
        for (var minutes = startMinutes; minutes <= endMinutes; minutes += intervalMinutes)
        {
            var slotHour = minutes / 60;
            var slotMinute = minutes % 60;
            var slotTime = new TimeSpan(slotHour, slotMinute, 0);
            var slotDateTime = date.Date.Add(slotTime);
            // 当前阶段仅做规则预览，不再伪造预约占用数据。
            var remaining = normalizedCapacity;
            var status = slotDateTime <= now.AddHours(minAdvanceHours)
                ? "expired"
                : remaining == 0
                    ? "full"
                    : remaining <= 1
                        ? "almost"
                        : "available";

            results.Add(new PickupPreviewSlotDto
            {
                Time = $"{slotHour:00}:{slotMinute:00}",
                RemainingCount = remaining,
                Status = status
            });
        }

        return results;
    }

    private static string GetWeekdayLabel(int dayOfWeek)
    {
        return dayOfWeek switch
        {
            0 => "周一",
            1 => "周二",
            2 => "周三",
            3 => "周四",
            4 => "周五",
            5 => "周六",
            6 => "周日",
            _ => "周一"
        };
    }
}
