using System.Globalization;
using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Application.App.Stores.Services;
using TakeoutSaaS.Domain.Merchants.Enums;
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;

namespace TakeoutSaaS.TenantApi.Controllers;

internal static class StoreApiHelpers
{
    private static readonly string[] AvatarColors =
    [
        "#f56a00",
        "#7265e6",
        "#52c41a",
        "#fa8c16",
        "#1890ff",
        "#bfbfbf",
        "#13c2c2",
        "#eb2f96"
    ];

    public static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
    {
        PropertyNameCaseInsensitive = true
    };

    public static (long TenantId, long MerchantId) GetTenantMerchantContext(StoreContextService storeContextService)
    {
        var (_, tenantId, merchantId) = storeContextService.GetRequiredContext();
        return (tenantId, merchantId);
    }

    public static long ParseRequiredSnowflake(string? value, string fieldName)
    {
        if (!long.TryParse(value, out var id) || id <= 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"{fieldName} 非法");
        }

        return id;
    }

    public static long? ParseSnowflakeOrNull(string? value)
    {
        return long.TryParse(value, out var id) && id > 0 ? id : null;
    }

    public static List<long> ParseSnowflakeList(IEnumerable<string>? values)
    {
        if (values is null)
        {
            return [];
        }

        return values
            .Select(ParseSnowflakeOrNull)
            .Where(id => id.HasValue)
            .Select(id => id!.Value)
            .Distinct()
            .ToList();
    }

    public static TimeSpan ParseRequiredTime(string? value, string fieldName)
    {
        if (string.IsNullOrWhiteSpace(value) ||
            !TimeSpan.TryParseExact(value, "hh\\:mm", CultureInfo.InvariantCulture, out var parsed))
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"{fieldName} 时间格式必须为 HH:mm");
        }

        return parsed;
    }

    public static string ToHHmm(TimeSpan value)
    {
        return value.ToString("hh\\:mm", CultureInfo.InvariantCulture);
    }

    public static string? ToHHmm(TimeSpan? value)
    {
        return value.HasValue ? ToHHmm(value.Value) : null;
    }

    public static DateTime ParseDateOnly(string? value, string fieldName)
    {
        if (string.IsNullOrWhiteSpace(value) ||
            !DateTime.TryParseExact(value, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsed))
        {
            throw new BusinessException(ErrorCodes.BadRequest, $"{fieldName} 日期格式必须为 yyyy-MM-dd");
        }

        // PostgreSQL timestamptz 仅接受 UTC，日期输入统一落盘为 UTC 零点。
        return DateTime.SpecifyKind(parsed.Date, DateTimeKind.Utc);
    }

    public static string ToDateOnly(DateTime value)
    {
        return value.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture);
    }

    public static DayOfWeek UiDayOfWeekToDotNet(int uiDayOfWeek)
    {
        return uiDayOfWeek switch
        {
            0 => DayOfWeek.Monday,
            1 => DayOfWeek.Tuesday,
            2 => DayOfWeek.Wednesday,
            3 => DayOfWeek.Thursday,
            4 => DayOfWeek.Friday,
            5 => DayOfWeek.Saturday,
            6 => DayOfWeek.Sunday,
            _ => throw new BusinessException(ErrorCodes.BadRequest, "dayOfWeek 必须在 0-6 之间")
        };
    }

    public static int DotNetDayOfWeekToUi(DayOfWeek dayOfWeek)
    {
        return dayOfWeek switch
        {
            DayOfWeek.Monday => 0,
            DayOfWeek.Tuesday => 1,
            DayOfWeek.Wednesday => 2,
            DayOfWeek.Thursday => 3,
            DayOfWeek.Friday => 4,
            DayOfWeek.Saturday => 5,
            DayOfWeek.Sunday => 6,
            _ => 0
        };
    }

    public static string SerializeWeekdays(IEnumerable<int>? uiDaysOfWeek)
    {
        var normalized = (uiDaysOfWeek ?? [])
            .Distinct()
            .Where(day => day is >= 0 and <= 6)
            .OrderBy(day => day)
            .ToList();

        return normalized.Count == 0 ? string.Empty : string.Join(',', normalized);
    }

    public static List<int> DeserializeWeekdays(string? storedValue)
    {
        if (string.IsNullOrWhiteSpace(storedValue))
        {
            return [];
        }

        var values = storedValue
            .Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
            .Select(item => int.TryParse(item, out var parsed) ? parsed : -1)
            .Select(day =>
            {
                // 兼容旧数据（1-7）
                if (day is >= 1 and <= 7)
                {
                    return day == 7 ? 6 : day - 1;
                }

                return day;
            })
            .Where(day => day is >= 0 and <= 6)
            .Distinct()
            .OrderBy(day => day)
            .ToList();

        return values;
    }

    public static BusinessHourType ToBusinessHourType(int slotType)
    {
        return slotType switch
        {
            1 => BusinessHourType.Normal,
            2 => BusinessHourType.PickupOrDelivery,
            3 => BusinessHourType.ReservationOnly,
            _ => throw new BusinessException(ErrorCodes.BadRequest, "slot.type 非法")
        };
    }

    public static int ToSlotType(BusinessHourType hourType)
    {
        return hourType switch
        {
            BusinessHourType.Normal => 1,
            BusinessHourType.PickupOrDelivery => 2,
            BusinessHourType.ReservationOnly => 3,
            _ => 1
        };
    }

    public static StaffRoleType ToStaffRoleType(string? value)
    {
        return (value ?? string.Empty).Trim().ToLowerInvariant() switch
        {
            "manager" => StaffRoleType.Admin,
            "cashier" => StaffRoleType.FrontDesk,
            "chef" => StaffRoleType.Kitchen,
            "courier" => StaffRoleType.Courier,
            _ => StaffRoleType.FrontDesk
        };
    }

    public static string ToStaffRoleTypeText(StaffRoleType value)
    {
        return value switch
        {
            StaffRoleType.Admin => "manager",
            StaffRoleType.FrontDesk => "cashier",
            StaffRoleType.Kitchen => "chef",
            StaffRoleType.Courier => "courier",
            StaffRoleType.Operator => "manager",
            _ => "cashier"
        };
    }

    public static StaffStatus ToStaffStatus(string? value)
    {
        return (value ?? string.Empty).Trim().ToLowerInvariant() switch
        {
            "active" => StaffStatus.Active,
            "leave" => StaffStatus.Disabled,
            "resigned" => StaffStatus.Resigned,
            _ => StaffStatus.Active
        };
    }

    public static string ToStaffStatusText(StaffStatus value)
    {
        return value switch
        {
            StaffStatus.Active => "active",
            StaffStatus.Disabled => "leave",
            StaffStatus.Resigned => "resigned",
            _ => "active"
        };
    }

    public static StoreTableStatus ToTableStatus(string? value)
    {
        return (value ?? string.Empty).Trim().ToLowerInvariant() switch
        {
            "free" => StoreTableStatus.Idle,
            "disabled" => StoreTableStatus.Disabled,
            "dining" => StoreTableStatus.Occupied,
            "reserved" => StoreTableStatus.Cleaning,
            _ => StoreTableStatus.Idle
        };
    }

    public static string ToTableStatusText(StoreTableStatus value)
    {
        return value switch
        {
            StoreTableStatus.Idle => "free",
            StoreTableStatus.Disabled => "disabled",
            StoreTableStatus.Occupied => "dining",
            StoreTableStatus.Cleaning => "reserved",
            _ => "free"
        };
    }

    public static StorePickupMode ToPickupMode(string? value)
    {
        return string.Equals(value, "fine", StringComparison.OrdinalIgnoreCase)
            ? StorePickupMode.Fine
            : StorePickupMode.Big;
    }

    public static string ToPickupModeText(StorePickupMode value)
    {
        return value == StorePickupMode.Fine ? "fine" : "big";
    }

    public static StoreDeliveryMode ToDeliveryMode(string? value)
    {
        return string.Equals(value, "polygon", StringComparison.OrdinalIgnoreCase)
            ? StoreDeliveryMode.Polygon
            : StoreDeliveryMode.Radius;
    }

    public static string ToDeliveryModeText(StoreDeliveryMode value)
    {
        return value == StoreDeliveryMode.Polygon ? "polygon" : "radius";
    }

    public static StoreStaffShiftType ToShiftType(string? value)
    {
        return (value ?? string.Empty).Trim().ToLowerInvariant() switch
        {
            "morning" => StoreStaffShiftType.Morning,
            "evening" => StoreStaffShiftType.Evening,
            "full" => StoreStaffShiftType.Full,
            "off" => StoreStaffShiftType.Off,
            _ => StoreStaffShiftType.Off
        };
    }

    public static string ToShiftTypeText(StoreStaffShiftType value)
    {
        return value switch
        {
            StoreStaffShiftType.Morning => "morning",
            StoreStaffShiftType.Evening => "evening",
            StoreStaffShiftType.Full => "full",
            StoreStaffShiftType.Off => "off",
            _ => "off"
        };
    }

    public static async Task<Store> EnsureStoreAccessibleAsync(
        TakeoutAppDbContext dbContext,
        long tenantId,
        long merchantId,
        long storeId,
        CancellationToken cancellationToken)
    {
        var store = await dbContext.Stores
            .FirstOrDefaultAsync(x => x.Id == storeId && x.TenantId == tenantId && x.MerchantId == merchantId, cancellationToken);

        if (store is null)
        {
            throw new BusinessException(ErrorCodes.NotFound, "门店不存在或无权限访问");
        }

        return store;
    }

    public static async Task<HashSet<long>> FilterAccessibleStoreIdsAsync(
        TakeoutAppDbContext dbContext,
        long tenantId,
        long merchantId,
        IEnumerable<long> storeIds,
        CancellationToken cancellationToken)
    {
        var ids = storeIds.Distinct().ToList();
        if (ids.Count == 0)
        {
            return [];
        }

        return await dbContext.Stores
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.MerchantId == merchantId && ids.Contains(x.Id))
            .Select(x => x.Id)
            .ToHashSetAsync(cancellationToken);
    }

    public static string ResolveAvatarColor(string? seed)
    {
        var source = string.IsNullOrWhiteSpace(seed) ? "store-staff" : seed;
        var hash = 0;
        foreach (var ch in source)
        {
            hash = (hash * 31 + ch) & int.MaxValue;
        }

        return AvatarColors[hash % AvatarColors.Length];
    }

    public static string ResolveWeekStartDate(string? requestedWeekStartDate)
    {
        if (!string.IsNullOrWhiteSpace(requestedWeekStartDate) &&
            DateTime.TryParseExact(requestedWeekStartDate, "yyyy-MM-dd", CultureInfo.InvariantCulture, DateTimeStyles.None, out var parsed))
        {
            return ToDateOnly(parsed);
        }

        var today = DateTime.UtcNow.Date;
        var diff = today.DayOfWeek == DayOfWeek.Sunday ? -6 : 1 - (int)today.DayOfWeek;
        var monday = today.AddDays(diff);
        return ToDateOnly(monday);
    }
}
