using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using TakeoutSaaS.Application.App.Stores.Services;
using TakeoutSaaS.Domain.Stores.Entities;
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 StoreDineInController(
    TakeoutAppDbContext dbContext,
    StoreContextService storeContextService) : BaseApiController
{
    /// <summary>
    /// 获取门店堂食设置。
    /// </summary>
    [HttpGet("dinein")]
    [ProducesResponseType(typeof(ApiResponse<StoreDineInSettingsDto>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<StoreDineInSettingsDto>> 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 basic = await dbContext.StoreDineInSettings
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StoreId == parsedStoreId, cancellationToken);
        var areas = await dbContext.StoreTableAreas
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
            .OrderBy(x => x.SortOrder)
            .ThenBy(x => x.Name)
            .ToListAsync(cancellationToken);
        var tables = await dbContext.StoreTables
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
            .OrderBy(x => x.TableCode)
            .ToListAsync(cancellationToken);
        var isConfigured = basic is not null || areas.Count > 0 || tables.Count > 0;

        return ApiResponse<StoreDineInSettingsDto>.Ok(new StoreDineInSettingsDto
        {
            StoreId = parsedStoreId.ToString(),
            IsConfigured = isConfigured,
            BasicSettings = basic is null
                ? null
                : new DineInBasicSettingsDto
                {
                    Enabled = basic.Enabled,
                    DefaultDiningMinutes = basic.DefaultDiningMinutes,
                    OvertimeReminderMinutes = basic.OvertimeReminderMinutes
                },
            Areas = areas.Select(MapArea).ToList(),
            Tables = tables.Select(MapTable).ToList()
        });
    }

    /// <summary>
    /// 保存门店堂食基础设置。
    /// </summary>
    [HttpPost("dinein/basic/save")]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<object>> SaveBasic([FromBody] SaveStoreDineInBasicSettingsRequest 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 basic = await dbContext.StoreDineInSettings
            .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StoreId == parsedStoreId, cancellationToken);
        if (basic is null)
        {
            basic = new StoreDineInSetting
            {
                StoreId = parsedStoreId
            };
            await dbContext.StoreDineInSettings.AddAsync(basic, cancellationToken);
        }

        basic.Enabled = request.BasicSettings.Enabled;
        basic.DefaultDiningMinutes = Math.Clamp(request.BasicSettings.DefaultDiningMinutes, 1, 999);
        basic.OvertimeReminderMinutes = Math.Clamp(request.BasicSettings.OvertimeReminderMinutes, 0, 999);

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

    /// <summary>
    /// 保存堂食区域。
    /// </summary>
    [HttpPost("dinein/area/save")]
    [ProducesResponseType(typeof(ApiResponse<DineInAreaDto>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<DineInAreaDto>> SaveArea([FromBody] SaveDineInAreaRequest 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 areaName = request.Area.Name?.Trim() ?? string.Empty;
        if (string.IsNullOrWhiteSpace(areaName))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "区域名称不能为空");
        }

        var areaId = StoreApiHelpers.ParseSnowflakeOrNull(request.Area.Id);
        StoreTableArea? area = null;
        if (areaId.HasValue)
        {
            area = await dbContext.StoreTableAreas.FirstOrDefaultAsync(
                x => x.Id == areaId.Value && x.TenantId == tenantId && x.StoreId == parsedStoreId,
                cancellationToken);
        }

        if (area is null)
        {
            area = new StoreTableArea
            {
                StoreId = parsedStoreId
            };
            await dbContext.StoreTableAreas.AddAsync(area, cancellationToken);
        }

        area.Name = areaName;
        area.Description = request.Area.Description?.Trim();
        area.SortOrder = Math.Max(1, request.Area.Sort);

        await dbContext.SaveChangesAsync(cancellationToken);
        return ApiResponse<DineInAreaDto>.Ok(MapArea(area));
    }

    /// <summary>
    /// 删除堂食区域。
    /// </summary>
    [HttpPost("dinein/area/delete")]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<object>> DeleteArea([FromBody] DeleteDineInAreaRequest request, CancellationToken cancellationToken)
    {
        var parsedStoreId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, "storeId");
        var parsedAreaId = StoreApiHelpers.ParseRequiredSnowflake(request.AreaId, "areaId");
        var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
        await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, parsedStoreId, cancellationToken);

        var hasTables = await dbContext.StoreTables
            .AnyAsync(x => x.TenantId == tenantId && x.StoreId == parsedStoreId && x.AreaId == parsedAreaId, cancellationToken);
        if (hasTables)
        {
            throw new BusinessException(ErrorCodes.Conflict, "该区域仍有桌位，请先迁移或删除桌位");
        }

        var area = await dbContext.StoreTableAreas.FirstOrDefaultAsync(
            x => x.Id == parsedAreaId && x.TenantId == tenantId && x.StoreId == parsedStoreId,
            cancellationToken);
        if (area is null)
        {
            return ApiResponse<object>.Ok(null);
        }

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

    /// <summary>
    /// 保存堂食桌位。
    /// </summary>
    [HttpPost("dinein/table/save")]
    [ProducesResponseType(typeof(ApiResponse<DineInTableDto>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<DineInTableDto>> SaveTable([FromBody] SaveDineInTableRequest 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 parsedAreaId = StoreApiHelpers.ParseRequiredSnowflake(request.Table.AreaId, "table.areaId");
        var areaExists = await dbContext.StoreTableAreas
            .AnyAsync(x => x.Id == parsedAreaId && x.TenantId == tenantId && x.StoreId == parsedStoreId, cancellationToken);
        if (!areaExists)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "区域不存在");
        }

        var tableCode = (request.Table.Code ?? string.Empty).Trim().ToUpperInvariant();
        if (string.IsNullOrWhiteSpace(tableCode))
        {
            throw new BusinessException(ErrorCodes.BadRequest, "桌位编号不能为空");
        }

        var tableId = StoreApiHelpers.ParseSnowflakeOrNull(request.Table.Id);
        StoreTable? table = null;
        if (tableId.HasValue)
        {
            table = await dbContext.StoreTables.FirstOrDefaultAsync(
                x => x.Id == tableId.Value && x.TenantId == tenantId && x.StoreId == parsedStoreId,
                cancellationToken);
        }

        var duplicateCode = await dbContext.StoreTables.AnyAsync(
            x => x.TenantId == tenantId
                 && x.StoreId == parsedStoreId
                 && x.TableCode == tableCode
                 && (!tableId.HasValue || x.Id != tableId.Value),
            cancellationToken);
        if (duplicateCode)
        {
            throw new BusinessException(ErrorCodes.Conflict, "桌位编号已存在");
        }

        if (table is null)
        {
            table = new StoreTable
            {
                StoreId = parsedStoreId
            };
            await dbContext.StoreTables.AddAsync(table, cancellationToken);
        }

        table.AreaId = parsedAreaId;
        table.TableCode = tableCode;
        table.Capacity = Math.Clamp(request.Table.Seats, 1, 20);
        table.Status = StoreApiHelpers.ToTableStatus(request.Table.Status);
        table.Tags = string.Join(',', (request.Table.Tags ?? [])
            .Select(x => x.Trim())
            .Where(x => !string.IsNullOrWhiteSpace(x))
            .Distinct());

        await dbContext.SaveChangesAsync(cancellationToken);
        return ApiResponse<DineInTableDto>.Ok(MapTable(table));
    }

    /// <summary>
    /// 删除堂食桌位。
    /// </summary>
    [HttpPost("dinein/table/delete")]
    [ProducesResponseType(typeof(ApiResponse<object>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<object>> DeleteTable([FromBody] DeleteDineInTableRequest request, CancellationToken cancellationToken)
    {
        var parsedStoreId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, "storeId");
        var parsedTableId = StoreApiHelpers.ParseRequiredSnowflake(request.TableId, "tableId");
        var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
        await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, parsedStoreId, cancellationToken);

        var table = await dbContext.StoreTables.FirstOrDefaultAsync(
            x => x.Id == parsedTableId && x.TenantId == tenantId && x.StoreId == parsedStoreId,
            cancellationToken);
        if (table is null)
        {
            return ApiResponse<object>.Ok(null);
        }

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

    /// <summary>
    /// 批量生成堂食桌位。
    /// </summary>
    [HttpPost("dinein/table/batch-create")]
    [ProducesResponseType(typeof(ApiResponse<BatchCreateDineInTablesResultDto>), StatusCodes.Status200OK)]
    public async Task<ApiResponse<BatchCreateDineInTablesResultDto>> BatchCreateTables(
        [FromBody] BatchCreateDineInTablesRequest request,
        CancellationToken cancellationToken)
    {
        var parsedStoreId = StoreApiHelpers.ParseRequiredSnowflake(request.StoreId, "storeId");
        var parsedAreaId = StoreApiHelpers.ParseRequiredSnowflake(request.AreaId, "areaId");
        var (tenantId, merchantId) = StoreApiHelpers.GetTenantMerchantContext(storeContextService);
        await StoreApiHelpers.EnsureStoreAccessibleAsync(dbContext, tenantId, merchantId, parsedStoreId, cancellationToken);

        var areaExists = await dbContext.StoreTableAreas
            .AnyAsync(x => x.Id == parsedAreaId && x.TenantId == tenantId && x.StoreId == parsedStoreId, cancellationToken);
        if (!areaExists)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "区域不存在");
        }

        var count = Math.Clamp(request.Count, 1, 50);
        var startNumber = Math.Clamp(request.StartNumber, 1, 9999);
        var seats = Math.Clamp(request.Seats, 1, 20);
        var prefix = string.IsNullOrWhiteSpace(request.CodePrefix) ? "A" : request.CodePrefix.Trim().ToUpperInvariant();
        var width = Math.Max(2, (startNumber + count - 1).ToString().Length);

        var existingCodes = await dbContext.StoreTables
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.StoreId == parsedStoreId)
            .Select(x => x.TableCode)
            .ToListAsync(cancellationToken);
        var existingCodeSet = new HashSet<string>(existingCodes, StringComparer.OrdinalIgnoreCase);

        var created = new List<StoreTable>();
        for (var i = 0; i < count; i++)
        {
            var code = $"{prefix}{(startNumber + i).ToString().PadLeft(width, '0')}";
            if (existingCodeSet.Contains(code))
            {
                continue;
            }

            var table = new StoreTable
            {
                StoreId = parsedStoreId,
                AreaId = parsedAreaId,
                TableCode = code,
                Capacity = seats,
                Status = StoreApiHelpers.ToTableStatus("free"),
                Tags = null
            };
            created.Add(table);
            existingCodeSet.Add(code);
        }

        if (created.Count > 0)
        {
            await dbContext.StoreTables.AddRangeAsync(created, cancellationToken);
            await dbContext.SaveChangesAsync(cancellationToken);
        }

        return ApiResponse<BatchCreateDineInTablesResultDto>.Ok(new BatchCreateDineInTablesResultDto
        {
            CreatedTables = created.Select(MapTable).ToList()
        });
    }

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

        var sourceBasic = await dbContext.StoreDineInSettings
            .AsNoTracking()
            .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StoreId == sourceStoreId, cancellationToken);
        var sourceAreas = await dbContext.StoreTableAreas
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.StoreId == sourceStoreId)
            .OrderBy(x => x.SortOrder)
            .ThenBy(x => x.Id)
            .ToListAsync(cancellationToken);
        var sourceTables = await dbContext.StoreTables
            .AsNoTracking()
            .Where(x => x.TenantId == tenantId && x.StoreId == sourceStoreId)
            .OrderBy(x => x.TableCode)
            .ToListAsync(cancellationToken);
        if (sourceBasic is null)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "源门店未配置堂食基础设置，无法复制");
        }

        foreach (var targetStoreId in accessibleTargetIds)
        {
            var targetBasic = await dbContext.StoreDineInSettings
                .FirstOrDefaultAsync(x => x.TenantId == tenantId && x.StoreId == targetStoreId, cancellationToken);
            if (targetBasic is null)
            {
                targetBasic = new StoreDineInSetting
                {
                    StoreId = targetStoreId
                };
                await dbContext.StoreDineInSettings.AddAsync(targetBasic, cancellationToken);
            }

            targetBasic.Enabled = sourceBasic.Enabled;
            targetBasic.DefaultDiningMinutes = sourceBasic.DefaultDiningMinutes;
            targetBasic.OvertimeReminderMinutes = sourceBasic.OvertimeReminderMinutes;

            var targetTables = await dbContext.StoreTables
                .Where(x => x.TenantId == tenantId && x.StoreId == targetStoreId)
                .ToListAsync(cancellationToken);
            var targetAreas = await dbContext.StoreTableAreas
                .Where(x => x.TenantId == tenantId && x.StoreId == targetStoreId)
                .ToListAsync(cancellationToken);
            dbContext.StoreTables.RemoveRange(targetTables);
            dbContext.StoreTableAreas.RemoveRange(targetAreas);
            await dbContext.SaveChangesAsync(cancellationToken);

            var createdAreas = sourceAreas.Select(area => new StoreTableArea
            {
                StoreId = targetStoreId,
                Name = area.Name,
                Description = area.Description,
                SortOrder = area.SortOrder
            }).ToList();
            if (createdAreas.Count > 0)
            {
                await dbContext.StoreTableAreas.AddRangeAsync(createdAreas, cancellationToken);
                await dbContext.SaveChangesAsync(cancellationToken);
            }

            var areaIdMap = new Dictionary<long, long>();
            for (var index = 0; index < sourceAreas.Count && index < createdAreas.Count; index++)
            {
                areaIdMap[sourceAreas[index].Id] = createdAreas[index].Id;
            }

            var createdTables = sourceTables.Select(table => new StoreTable
            {
                StoreId = targetStoreId,
                AreaId = table.AreaId.HasValue && areaIdMap.TryGetValue(table.AreaId.Value, out var mappedAreaId)
                    ? mappedAreaId
                    : null,
                TableCode = table.TableCode,
                Capacity = table.Capacity,
                Status = table.Status,
                Tags = table.Tags
            }).ToList();
            if (createdTables.Count > 0)
            {
                await dbContext.StoreTables.AddRangeAsync(createdTables, cancellationToken);
                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }

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

    private static DineInAreaDto MapArea(StoreTableArea source)
    {
        return new DineInAreaDto
        {
            Id = source.Id.ToString(),
            Name = source.Name,
            Description = source.Description ?? string.Empty,
            Sort = source.SortOrder
        };
    }

    private static DineInTableDto MapTable(StoreTable source)
    {
        return new DineInTableDto
        {
            Id = source.Id.ToString(),
            AreaId = source.AreaId?.ToString() ?? string.Empty,
            Code = source.TableCode,
            Seats = source.Capacity,
            Status = StoreApiHelpers.ToTableStatusText(source.Status),
            Tags = string.IsNullOrWhiteSpace(source.Tags)
                ? []
                : source.Tags.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries).ToList()
        };
    }
}
