using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
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.Tenancy;

namespace TakeoutSaaS.Infrastructure.App.Services;

/// <summary>
/// 门店定时任务服务实现。
/// </summary>
public sealed class StoreSchedulerService(
    TakeoutAppDbContext context,
    ITenantContextAccessor tenantContextAccessor,
    ILogger<StoreSchedulerService> logger)
    : IStoreSchedulerService
{
    /// <inheritdoc />
    public async Task<int> AutoSwitchBusinessStatusAsync(DateTime now, CancellationToken cancellationToken)
    {
        var currentTenantId = tenantContextAccessor.Current?.TenantId ?? 0;
        if (currentTenantId != 0)
        {
            return await AutoSwitchBusinessStatusSingleTenantAsync(now, cancellationToken);
        }

        var tenants = await context.Tenants
            .AsNoTracking()
            .Where(x => x.DeletedAt == null && x.Id > 0)
            .Select(x => new { x.Id, x.Code })
            .ToListAsync(cancellationToken);
        if (tenants.Count == 0)
        {
            return 0;
        }

        var totalUpdated = 0;
        foreach (var tenant in tenants)
        {
            using (tenantContextAccessor.EnterTenantScope(tenant.Id, "scheduler", tenant.Code))
            {
                totalUpdated += await AutoSwitchBusinessStatusSingleTenantAsync(now, cancellationToken);
            }
        }

        return totalUpdated;
    }

    private async Task<int> AutoSwitchBusinessStatusSingleTenantAsync(DateTime now, CancellationToken cancellationToken)
    {
        // 1. 读取候选门店
        var stores = await context.Stores
            .Where(store => store.DeletedAt == null
                && store.AuditStatus == StoreAuditStatus.Activated
                && store.BusinessStatus != StoreBusinessStatus.ForceClosed)
            .ToListAsync(cancellationToken);
        if (stores.Count == 0)
        {
            return 0;
        }

        // 2. (空行后) 读取营业时段与休息日
        var storeIds = stores.Select(store => store.Id).ToArray();
        var hours = await context.StoreBusinessHours
            .AsNoTracking()
            .Where(hour => storeIds.Contains(hour.StoreId))
            .ToListAsync(cancellationToken);
        var today = now.Date;
        var holidays = await context.StoreHolidays
            .AsNoTracking()
            .Where(holiday => storeIds.Contains(holiday.StoreId)
                && holiday.Date <= today
                && (holiday.EndDate == null || holiday.EndDate >= today))
            .ToListAsync(cancellationToken);

        // 3. (空行后) 构造查找表
        var hoursLookup = hours
            .GroupBy(hour => hour.StoreId)
            .ToDictionary(group => group.Key, group => (IReadOnlyList<StoreBusinessHour>)group.ToList());
        var holidayLookup = holidays
            .GroupBy(holiday => holiday.StoreId)
            .ToDictionary(group => group.Key, group => (IReadOnlyList<StoreHoliday>)group.ToList());

        // 4. (空行后) 判定状态并更新
        var updated = 0;
        foreach (var store in stores)
        {
            // 4.1 跳过强制关闭门店
            if (store.BusinessStatus == StoreBusinessStatus.ForceClosed)
            {
                continue;
            }

            // 4.2 (空行后) 尊重手动歇业原因
            if (store.ClosureReason.HasValue && store.ClosureReason != StoreClosureReason.OutOfBusinessHours)
            {
                continue;
            }

            // 4.3 (空行后) 计算营业状态
            var storeHolidays = holidayLookup.TryGetValue(store.Id, out var matched) ? matched : [];
            var nowTime = now.TimeOfDay;
            var isHolidayClosed = storeHolidays.Any(holiday =>
                holiday.OverrideType == OverrideType.Closed && IsWithinHolidayTime(holiday, nowTime));
            var hasModifiedHours = storeHolidays.Any(holiday => holiday.OverrideType == OverrideType.ModifiedHours);
            var isModifiedOpen = hasModifiedHours && storeHolidays.Any(holiday =>
                holiday.OverrideType == OverrideType.ModifiedHours && IsWithinHolidayTime(holiday, nowTime));
            var isTemporaryOpen = storeHolidays.Any(holiday =>
                holiday.OverrideType == OverrideType.TemporaryOpen && IsWithinHolidayTime(holiday, nowTime));
            var hasHours = hoursLookup.TryGetValue(store.Id, out var storeHours) && storeHours.Count > 0;
            var isOpen = false;
            if (isHolidayClosed)
            {
                isOpen = false;
            }
            else if (hasModifiedHours)
            {
                isOpen = isModifiedOpen;
            }
            else
            {
                isOpen = hasHours && IsWithinBusinessHours(storeHours ?? [], now);
                if (!isOpen && isTemporaryOpen)
                {
                    isOpen = true;
                }
            }
            if (isOpen)
            {
                if (store.BusinessStatus != StoreBusinessStatus.Open)
                {
                    store.BusinessStatus = StoreBusinessStatus.Open;
                    store.ClosureReason = null;
                    store.ClosureReasonText = null;
                    updated++;
                }
                continue;
            }

            // 4.4 (空行后) 非营业时段切换为休息
            if (store.BusinessStatus != StoreBusinessStatus.Resting || store.ClosureReason != StoreClosureReason.OutOfBusinessHours)
            {
                store.BusinessStatus = StoreBusinessStatus.Resting;
                store.ClosureReason = StoreClosureReason.OutOfBusinessHours;
                store.ClosureReasonText = "非营业时间自动休息";
                updated++;
            }
        }

        // 5. (空行后) 保存变更并记录日志
        if (updated > 0)
        {
            await context.SaveChangesAsync(cancellationToken);
        }

        logger.LogInformation("定时任务：营业状态自动切换完成，更新 {UpdatedCount} 家门店", updated);
        return updated;
    }

    /// <inheritdoc />
    public async Task<int> CheckQualificationExpiryAsync(DateTime now, CancellationToken cancellationToken)
    {
        var currentTenantId = tenantContextAccessor.Current?.TenantId ?? 0;
        if (currentTenantId != 0)
        {
            return await CheckQualificationExpirySingleTenantAsync(now, cancellationToken);
        }

        var tenants = await context.Tenants
            .AsNoTracking()
            .Where(x => x.DeletedAt == null && x.Id > 0)
            .Select(x => new { x.Id, x.Code })
            .ToListAsync(cancellationToken);
        if (tenants.Count == 0)
        {
            return 0;
        }

        var totalUpdated = 0;
        foreach (var tenant in tenants)
        {
            using (tenantContextAccessor.EnterTenantScope(tenant.Id, "scheduler", tenant.Code))
            {
                totalUpdated += await CheckQualificationExpirySingleTenantAsync(now, cancellationToken);
            }
        }

        return totalUpdated;
    }

    private async Task<int> CheckQualificationExpirySingleTenantAsync(DateTime now, CancellationToken cancellationToken)
    {
        // 1. 查询过期门店
        var today = DateOnly.FromDateTime(now);
        var expiredStoreIds = await context.StoreQualifications
            .AsNoTracking()
            .Where(qualification => qualification.DeletedAt == null
                && qualification.ExpiresAt.HasValue
                && qualification.ExpiresAt.Value < today)
            .Select(qualification => qualification.StoreId)
            .Distinct()
            .ToListAsync(cancellationToken);
        if (expiredStoreIds.Count == 0)
        {
            return 0;
        }

        // 2. (空行后) 加载门店并更新状态
        var stores = await context.Stores
            .Where(store => expiredStoreIds.Contains(store.Id)
                && store.DeletedAt == null
                && store.AuditStatus == StoreAuditStatus.Activated
                && store.BusinessStatus != StoreBusinessStatus.ForceClosed)
            .ToListAsync(cancellationToken);

        var updated = 0;
        foreach (var store in stores)
        {
            // 2.1 跳过已标记过期门店
            if (store.BusinessStatus == StoreBusinessStatus.Resting && store.ClosureReason == StoreClosureReason.LicenseExpired)
            {
                continue;
            }

            // 2.2 (空行后) 设置资质过期状态
            store.BusinessStatus = StoreBusinessStatus.Resting;
            store.ClosureReason = StoreClosureReason.LicenseExpired;
            store.ClosureReasonText = "证照过期自动休息";
            updated++;
        }

        // 3. (空行后) 保存变更并记录日志
        if (updated > 0)
        {
            await context.SaveChangesAsync(cancellationToken);
        }

        logger.LogInformation("定时任务：资质过期检查完成，更新 {UpdatedCount} 家门店", updated);
        return updated;
    }

    private static bool IsWithinBusinessHours(IReadOnlyList<StoreBusinessHour> hours, DateTime now)
    {
        // 1. 提取当前时间
        var day = now.DayOfWeek;
        var time = now.TimeOfDay;

        foreach (var hour in hours)
        {
            if (hour.HourType == BusinessHourType.Closed)
            {
                continue;
            }
            if (hour.StartTime == hour.EndTime)
            {
                continue;
            }
            if (hour.StartTime < hour.EndTime)
            {
                if (hour.DayOfWeek == day && time >= hour.StartTime && time < hour.EndTime)
                {
                    return true;
                }
                continue;
            }
            var nextDay = NextDay(hour.DayOfWeek);
            if (hour.DayOfWeek == day && time >= hour.StartTime)
            {
                return true;
            }
            if (nextDay == day && time < hour.EndTime)
            {
                return true;
            }
        }

        return false;
    }

    private static bool IsWithinHolidayTime(StoreHoliday holiday, TimeSpan time)
    {
        if (holiday.IsAllDay)
        {
            return true;
        }

        if (!holiday.StartTime.HasValue || !holiday.EndTime.HasValue)
        {
            return false;
        }

        return time >= holiday.StartTime.Value && time < holiday.EndTime.Value;
    }

    private static DayOfWeek NextDay(DayOfWeek day)
    {
        var next = (int)day + 1;
        return next > 6 ? DayOfWeek.Sunday : (DayOfWeek)next;
    }
}
