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 StoreHoursController(
    TakeoutAppDbContext dbContext,
    StoreContextService storeContextService) : BaseApiController
{
    /// <summary>
    /// 获取门店营业时间。
    /// </summary>
    [HttpGet("hours")]
    [ProducesResponseType(typeof(ApiResponse<StoreHoursDto>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<StoreHoursDto>> 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 hours = await dbContext.StoreBusinessHours
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
            .OrderBy(x => x.DayOfWeek)
            .ThenBy(x => x.StartTime)
            .ToListAsync(cancellationToken);

        var holidays = await dbContext.StoreHolidays
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
            .OrderBy(x => x.Date)
            .ThenBy(x => x.Id)
            .ToListAsync(cancellationToken);

        var weeklyHours = Enumerable.Range(0, 7)
            .Select(day =>
            {
                var slots = hours
                    .Where(x => StoreApiHelpers.DotNetDayOfWeekToUi(x.DayOfWeek) == day)
                    .Select(MapSlot)
                    .ToList();

                return new StoreHourDayHoursDto
                {
                    DayOfWeek = day,
                    IsOpen = slots.Count > 0,
                    Slots = slots
                };
            })
            .ToList();

        var result = new StoreHoursDto
        {
            StoreId = parsedStoreId.ToString(),
            WeeklyHours = weeklyHours,
            Holidays = holidays.Select(MapHoliday).ToList()
        };

        return ApiResponse<StoreHoursDto>.Ok(result);
    }

    /// <summary>
    /// 保存每周营业时间。
    /// </summary>
    [HttpPost("hours/weekly")]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<object>> SaveWeekly([FromBody] SaveWeeklyHoursRequest 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 existingHours = await dbContext.StoreBusinessHours
            .Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
            .ToListAsync(cancellationToken);

        dbContext.StoreBusinessHours.RemoveRange(existingHours);

        var toCreate = new List<StoreBusinessHour>();
        foreach (var day in request.WeeklyHours ?? [])
        {
            if (day.DayOfWeek is < 0 or > 6 || !day.IsOpen)
            {
                continue;
            }

            foreach (var slot in day.Slots ?? [])
            {
                var startTime = StoreApiHelpers.ParseRequiredTime(slot.StartTime, "slot.startTime");
                var endTime = StoreApiHelpers.ParseRequiredTime(slot.EndTime, "slot.endTime");
                if (startTime >= endTime)
                {
                    throw new BusinessException(ErrorCodes.BadRequest, "营业时段开始时间必须早于结束时间");
                }

                var hourType = StoreApiHelpers.ToBusinessHourType(slot.Type);
                toCreate.Add(new StoreBusinessHour
                {
                    StoreId = parsedStoreId,
                    DayOfWeek = StoreApiHelpers.UiDayOfWeekToDotNet(day.DayOfWeek),
                    HourType = hourType,
                    StartTime = startTime,
                    EndTime = endTime,
                    CapacityLimit = hourType == BusinessHourType.PickupOrDelivery ? slot.Capacity : null,
                    Notes = string.IsNullOrWhiteSpace(slot.Remark) ? null : slot.Remark.Trim()
                });
            }
        }

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

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

    /// <summary>
    /// 保存特殊日期。
    /// </summary>
    [HttpPost("hours/holiday")]
    [ProducesResponseType(typeof(ApiResponse<StoreHourHolidayDto>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<StoreHourHolidayDto>> SaveHoliday([FromBody] SaveHolidayRequest 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 holidayInput = request.Holiday;
        var startDate = StoreApiHelpers.ParseDateOnly(holidayInput.StartDate, "holiday.startDate");
        var endDate = StoreApiHelpers.ParseDateOnly(string.IsNullOrWhiteSpace(holidayInput.EndDate) ? holidayInput.StartDate : holidayInput.EndDate, "holiday.endDate");
        if (startDate > endDate)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "holiday.startDate 不能晚于 holiday.endDate");
        }

        var type = holidayInput.Type == 2 ? StoreHolidayType.Special : StoreHolidayType.Closed;
        var hasTimeRange = type == StoreHolidayType.Special
                           && !string.IsNullOrWhiteSpace(holidayInput.StartTime)
                           && !string.IsNullOrWhiteSpace(holidayInput.EndTime);

        var startTime = hasTimeRange ? StoreApiHelpers.ParseRequiredTime(holidayInput.StartTime, "holiday.startTime") : (TimeSpan?)null;
        var endTime = hasTimeRange ? StoreApiHelpers.ParseRequiredTime(holidayInput.EndTime, "holiday.endTime") : (TimeSpan?)null;
        if (hasTimeRange && startTime >= endTime)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "holiday.startTime 必须早于 holiday.endTime");
        }

        var holidayId = StoreApiHelpers.ParseSnowflakeOrNull(holidayInput.Id);

        // 同一门店的特殊日期范围不允许重叠（编辑时排除自身）。
        var hasOverlap = await dbContext.StoreHolidays
            .AsNoTracking()
            .AnyAsync(
                x => x.TenantId == tenantId
                     && x.StoreId == parsedStoreId
                     && (!holidayId.HasValue || x.Id != holidayId.Value)
                     && x.Date <= endDate
                     && (x.EndDate ?? x.Date) >= startDate,
                cancellationToken);
        if (hasOverlap)
        {
            throw new BusinessException(ErrorCodes.ValidationFailed, "日期范围与已有节假日/特殊日期冲突，请勿重复设置");
        }

        var entity = holidayId.HasValue
            ? await dbContext.StoreHolidays.FirstOrDefaultAsync(
                x => x.Id == holidayId.Value && x.TenantId == tenantId && x.StoreId == parsedStoreId,
                cancellationToken)
            : null;

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

        entity.Date = startDate;
        entity.EndDate = endDate;
        entity.IsAllDay = !hasTimeRange;
        entity.StartTime = hasTimeRange ? startTime : null;
        entity.EndTime = hasTimeRange ? endTime : null;
        entity.OverrideType = type == StoreHolidayType.Closed
            ? OverrideType.Closed
            : hasTimeRange
                ? OverrideType.ModifiedHours
                : OverrideType.TemporaryOpen;
        entity.IsClosed = type == StoreHolidayType.Closed;
        entity.Reason = string.IsNullOrWhiteSpace(holidayInput.Reason) ? null : holidayInput.Reason.Trim();

        await dbContext.SaveChangesAsync(cancellationToken);

        var result = MapHoliday(entity);
        result.Remark = holidayInput.Remark;
        return ApiResponse<StoreHourHolidayDto>.Ok(result);
    }

    /// <summary>
    /// 删除特殊日期。
    /// </summary>
    [HttpPost("hours/holiday/delete")]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<object>> DeleteHoliday([FromBody] DeleteHolidayRequest request, CancellationToken cancellationToken)
    {
        var holidayId = StoreApiHelpers.ParseRequiredSnowflake(request.Id, "id");
        var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);

        var holiday = await dbContext.StoreHolidays
            .FirstOrDefaultAsync(x => x.Id == holidayId && x.TenantId == tenantId, cancellationToken);

        if (holiday is null)
        {
            return ApiResponse<object>.Ok(null);
        }

        var hasAccess = await dbContext.Stores
            .AsNoTracking()
            .AnyAsync(x => x.Id == holiday.StoreId && x.TenantId == tenantId && x.MerchantId == merchantId, cancellationToken);
        if (!hasAccess)
        {
            throw new BusinessException(ErrorCodes.NotFound, "特殊日期不存在或无权限访问");
        }

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

    /// <summary>
    /// 复制营业时间到其他门店。
    /// </summary>
    [HttpPost("hours/copy")]
    [ProducesResponseType(typeof(ApiResponse<CopyStoreHoursResult>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<CopyStoreHoursResult>> Copy([FromBody] CopyStoreHoursRequest request, CancellationToken cancellationToken)
    {
        var sourceStoreId = StoreApiHelpers.ParseRequiredSnowflake(request.SourceStoreId, "sourceStoreId");
        var targetStoreIds = StoreApiHelpers.ParseSnowflakeList(request.TargetStoreIds);
        var includeWeeklyHours = request.IncludeWeeklyHours ?? true;
        var includeHolidays = request.IncludeHolidays ?? true;

        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<CopyStoreHoursResult>.Ok(new CopyStoreHoursResult
            {
                CopiedCount = 0,
                IncludeWeeklyHours = includeWeeklyHours,
                IncludeHolidays = includeHolidays
            });
        }

        if (includeWeeklyHours)
        {
            var sourceHours = await dbContext.StoreBusinessHours
                .AsNoTracking()
                .Where(x => x.TenantId == tenantId && x.StoreId == sourceStoreId)
                .ToListAsync(cancellationToken);

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

            var clonedHours = accessibleTargetIds
                .SelectMany(targetStoreId => sourceHours.Select(hour => new StoreBusinessHour
                {
                    StoreId = targetStoreId,
                    DayOfWeek = hour.DayOfWeek,
                    HourType = hour.HourType,
                    StartTime = hour.StartTime,
                    EndTime = hour.EndTime,
                    CapacityLimit = hour.CapacityLimit,
                    Notes = hour.Notes
                }))
                .ToList();

            if (clonedHours.Count > 0)
            {
                await dbContext.StoreBusinessHours.AddRangeAsync(clonedHours, cancellationToken);
            }
        }

        if (includeHolidays)
        {
            var sourceHolidays = await dbContext.StoreHolidays
                .AsNoTracking()
                .Where(x => x.TenantId == tenantId && x.StoreId == sourceStoreId)
                .ToListAsync(cancellationToken);

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

            var clonedHolidays = accessibleTargetIds
                .SelectMany(targetStoreId => sourceHolidays.Select(holiday => new StoreHoliday
                {
                    StoreId = targetStoreId,
                    Date = holiday.Date,
                    EndDate = holiday.EndDate,
                    IsAllDay = holiday.IsAllDay,
                    StartTime = holiday.StartTime,
                    EndTime = holiday.EndTime,
                    OverrideType = holiday.OverrideType,
                    IsClosed = holiday.IsClosed,
                    Reason = holiday.Reason
                }))
                .ToList();

            if (clonedHolidays.Count > 0)
            {
                await dbContext.StoreHolidays.AddRangeAsync(clonedHolidays, cancellationToken);
            }
        }

        await dbContext.SaveChangesAsync(cancellationToken);

        return ApiResponse<CopyStoreHoursResult>.Ok(new CopyStoreHoursResult
        {
            CopiedCount = accessibleTargetIds.Count,
            IncludeWeeklyHours = includeWeeklyHours,
            IncludeHolidays = includeHolidays
        });
    }

    private static StoreHourTimeSlotDto MapSlot(StoreBusinessHour source)
    {
        return new StoreHourTimeSlotDto
        {
            Id = source.Id.ToString(),
            Type = StoreApiHelpers.ToSlotType(source.HourType),
            StartTime = StoreApiHelpers.ToHHmm(source.StartTime),
            EndTime = StoreApiHelpers.ToHHmm(source.EndTime),
            Capacity = source.CapacityLimit,
            Remark = source.Notes
        };
    }

    private static StoreHourHolidayDto MapHoliday(StoreHoliday source)
    {
        var type = source.OverrideType == OverrideType.Closed || source.IsClosed
            ? StoreHolidayType.Closed
            : StoreHolidayType.Special;

        return new StoreHourHolidayDto
        {
            Id = source.Id.ToString(),
            StartDate = StoreApiHelpers.ToDateOnly(source.Date),
            EndDate = StoreApiHelpers.ToDateOnly(source.EndDate ?? source.Date),
            Type = (int)type,
            StartTime = type == StoreHolidayType.Special ? StoreApiHelpers.ToHHmm(source.StartTime) : null,
            EndTime = type == StoreHolidayType.Special ? StoreApiHelpers.ToHHmm(source.EndTime) : null,
            Reason = source.Reason ?? string.Empty
        };
    }

    private enum StoreHolidayType
    {
        Closed = 1,
        Special = 2
    }
}
