using Microsoft.Extensions.Logging;
using TakeoutSaaS.Application.Dictionary.Abstractions;
using TakeoutSaaS.Application.Dictionary.Contracts;
using TakeoutSaaS.Application.Dictionary.Models;
using TakeoutSaaS.Domain.Dictionary.Entities;
using TakeoutSaaS.Domain.Dictionary.Enums;
using TakeoutSaaS.Domain.Dictionary.Repositories;
using TakeoutSaaS.Shared.Abstractions.Constants;
using TakeoutSaaS.Shared.Abstractions.Exceptions;
using TakeoutSaaS.Shared.Abstractions.Tenancy;

namespace TakeoutSaaS.Application.Dictionary.Services;

/// <summary>
/// 参数字典应用服务实现。
/// </summary>
public sealed class DictionaryAppService(
    IDictionaryRepository repository,
    IDictionaryCache cache,
    ITenantProvider tenantProvider,
    ILogger<DictionaryAppService> logger) : IDictionaryAppService
{
    /// <summary>
    /// 创建字典分组。
    /// </summary>
    /// <param name="request">创建请求。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>分组 DTO。</returns>
    public async Task<DictionaryGroupDto> CreateGroupAsync(CreateDictionaryGroupRequest request, CancellationToken cancellationToken = default)
    {
        // 1. 规范化编码并确定租户
        var normalizedCode = NormalizeCode(request.Code);
        var targetTenant = ResolveTargetTenant(request.Scope);

        // 2. 校验编码唯一
        var existing = await repository.FindGroupByCodeAsync(normalizedCode, cancellationToken);
        if (existing != null)
        {
            throw new BusinessException(ErrorCodes.Conflict, $"字典分组编码 {normalizedCode} 已存在");
        }

        // 3. 构建分组实体
        var group = new DictionaryGroup
        {
            Id = 0,
            TenantId = targetTenant,
            Code = normalizedCode,
            Name = request.Name.Trim(),
            Scope = request.Scope,
            Description = request.Description?.Trim(),
            IsEnabled = true
        };

        // 4. 持久化并返回
        await repository.AddGroupAsync(group, cancellationToken);
        await repository.SaveChangesAsync(cancellationToken);
        logger.LogInformation("创建字典分组：{Code}({Scope})", group.Code, group.Scope);
        return MapGroup(group, includeItems: false);
    }

    /// <summary>
    /// 更新字典分组。
    /// </summary>
    /// <param name="groupId">分组 ID。</param>
    /// <param name="request">更新请求。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>分组 DTO。</returns>
    public async Task<DictionaryGroupDto> UpdateGroupAsync(long groupId, UpdateDictionaryGroupRequest request, CancellationToken cancellationToken = default)
    {
        // 1. 读取分组并校验权限
        var group = await RequireGroupAsync(groupId, cancellationToken);
        EnsureScopePermission(group.Scope);

        // 2. 更新字段
        group.Name = request.Name.Trim();
        group.Description = request.Description?.Trim();
        group.IsEnabled = request.IsEnabled;

        // 3. 持久化并失效缓存
        await repository.SaveChangesAsync(cancellationToken);
        await InvalidateCacheAsync(group, cancellationToken);
        logger.LogInformation("更新字典分组：{GroupId}", group.Id);
        return MapGroup(group, includeItems: false);
    }

    /// <summary>
    /// 删除字典分组。
    /// </summary>
    /// <param name="groupId">分组 ID。</param>
    /// <param name="cancellationToken">取消标记。</param>
    public async Task DeleteGroupAsync(long groupId, CancellationToken cancellationToken = default)
    {
        // 1. 读取分组并校验权限
        var group = await RequireGroupAsync(groupId, cancellationToken);
        EnsureScopePermission(group.Scope);

        // 2. 删除并失效缓存
        await repository.RemoveGroupAsync(group, cancellationToken);
        await repository.SaveChangesAsync(cancellationToken);
        await InvalidateCacheAsync(group, cancellationToken);
        logger.LogInformation("删除字典分组：{GroupId}", group.Id);
    }

    /// <summary>
    /// 搜索字典分组。
    /// </summary>
    /// <param name="request">查询条件。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>分组列表。</returns>
    public async Task<IReadOnlyList<DictionaryGroupDto>> SearchGroupsAsync(DictionaryGroupQuery request, CancellationToken cancellationToken = default)
    {
        // 1. 确定查询范围并校验权限
        var tenantId = tenantProvider.GetCurrentTenantId();
        var scope = ResolveScopeForQuery(request.Scope, tenantId);
        EnsureScopePermission(scope);

        // 2. 查询分组及可选项
        var groups = await repository.SearchGroupsAsync(scope, cancellationToken);
        var includeItems = request.IncludeItems;
        var result = new List<DictionaryGroupDto>(groups.Count);

        foreach (var group in groups)
        {
            IReadOnlyList<DictionaryItemDto> items = Array.Empty<DictionaryItemDto>();
            if (includeItems)
            {
                // 查询分组下字典项
                var itemEntities = await repository.GetItemsByGroupIdAsync(group.Id, cancellationToken);
                items = itemEntities.Select(MapItem).ToList();
            }

            result.Add(MapGroup(group, includeItems, items));
        }

        return result;
    }

    /// <summary>
    /// 创建字典项。
    /// </summary>
    /// <param name="request">创建请求。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>字典项 DTO。</returns>
    public async Task<DictionaryItemDto> CreateItemAsync(CreateDictionaryItemRequest request, CancellationToken cancellationToken = default)
    {
        // 1. 校验分组与权限
        var group = await RequireGroupAsync(request.GroupId, cancellationToken);
        EnsureScopePermission(group.Scope);

        // 2. 构建字典项
        var item = new DictionaryItem
        {
            Id = 0,
            TenantId = group.TenantId,
            GroupId = group.Id,
            Key = request.Key.Trim(),
            Value = request.Value.Trim(),
            Description = request.Description?.Trim(),
            SortOrder = request.SortOrder,
            IsDefault = request.IsDefault,
            IsEnabled = request.IsEnabled
        };

        // 3. 持久化并失效缓存
        await repository.AddItemAsync(item, cancellationToken);
        await repository.SaveChangesAsync(cancellationToken);
        await InvalidateCacheAsync(group, cancellationToken);
        logger.LogInformation("新增字典项：{ItemId}", item.Id);
        return MapItem(item);
    }

    /// <summary>
    /// 更新字典项。
    /// </summary>
    /// <param name="itemId">字典项 ID。</param>
    /// <param name="request">更新请求。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>字典项 DTO。</returns>
    public async Task<DictionaryItemDto> UpdateItemAsync(long itemId, UpdateDictionaryItemRequest request, CancellationToken cancellationToken = default)
    {
        // 1. 读取字典项与分组并校验权限
        var item = await RequireItemAsync(itemId, cancellationToken);
        var group = await RequireGroupAsync(item.GroupId, cancellationToken);
        EnsureScopePermission(group.Scope);

        // 2. 更新字段
        item.Value = request.Value.Trim();
        item.Description = request.Description?.Trim();
        item.SortOrder = request.SortOrder;
        item.IsDefault = request.IsDefault;
        item.IsEnabled = request.IsEnabled;

        // 3. 持久化并失效缓存
        await repository.SaveChangesAsync(cancellationToken);
        await InvalidateCacheAsync(group, cancellationToken);
        logger.LogInformation("更新字典项：{ItemId}", item.Id);
        return MapItem(item);
    }

    /// <summary>
    /// 删除字典项。
    /// </summary>
    /// <param name="itemId">字典项 ID。</param>
    /// <param name="cancellationToken">取消标记。</param>
    public async Task DeleteItemAsync(long itemId, CancellationToken cancellationToken = default)
    {
        // 1. 读取字典项与分组并校验权限
        var item = await RequireItemAsync(itemId, cancellationToken);
        var group = await RequireGroupAsync(item.GroupId, cancellationToken);
        EnsureScopePermission(group.Scope);

        // 2. 删除并失效缓存
        await repository.RemoveItemAsync(item, cancellationToken);
        await repository.SaveChangesAsync(cancellationToken);
        await InvalidateCacheAsync(group, cancellationToken);
        logger.LogInformation("删除字典项：{ItemId}", item.Id);
    }

    /// <summary>
    /// 批量获取缓存中的字典项。
    /// </summary>
    /// <param name="request">批量查询请求。</param>
    /// <param name="cancellationToken">取消标记。</param>
    /// <returns>按编码分组的字典项集合。</returns>
    public async Task<IReadOnlyDictionary<string, IReadOnlyList<DictionaryItemDto>>> GetCachedItemsAsync(DictionaryBatchQueryRequest request, CancellationToken cancellationToken = default)
    {
        // 1. 规范化编码
        var normalizedCodes = request.Codes
            .Where(code => !string.IsNullOrWhiteSpace(code))
            .Select(NormalizeCode)
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .ToArray();

        if (normalizedCodes.Length == 0)
        {
            return new Dictionary<string, IReadOnlyList<DictionaryItemDto>>(StringComparer.OrdinalIgnoreCase);
        }

        // 2. 按租户合并系统与业务字典
        var tenantId = tenantProvider.GetCurrentTenantId();
        var result = new Dictionary<string, IReadOnlyList<DictionaryItemDto>>(StringComparer.OrdinalIgnoreCase);

        foreach (var code in normalizedCodes)
        {
            var systemItems = await GetOrLoadCacheAsync(0, code, cancellationToken);
            if (tenantId == 0)
            {
                result[code] = systemItems;
                continue;
            }

            var tenantItems = await GetOrLoadCacheAsync(tenantId, code, cancellationToken);
            result[code] = MergeItems(systemItems, tenantItems);
        }

        return result;
    }

    private async Task<DictionaryGroup> RequireGroupAsync(long groupId, CancellationToken cancellationToken)
    {
        // 1. 读取分组，找不到抛异常
        var group = await repository.FindGroupByIdAsync(groupId, cancellationToken);
        if (group == null)
        {
            throw new BusinessException(ErrorCodes.NotFound, "字典分组不存在");
        }

        return group;
    }

    private async Task<DictionaryItem> RequireItemAsync(long itemId, CancellationToken cancellationToken)
    {
        // 1. 读取字典项，找不到抛异常
        var item = await repository.FindItemByIdAsync(itemId, cancellationToken);
        if (item == null)
        {
            throw new BusinessException(ErrorCodes.NotFound, "字典项不存在");
        }

        return item;
    }

    private long ResolveTargetTenant(DictionaryScope scope)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();
        if (scope == DictionaryScope.System)
        {
            EnsurePlatformTenant(tenantId);
            return 0;
        }

        if (tenantId == 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "业务参数需指定租户");
        }

        return tenantId;
    }

    private static string NormalizeCode(string code) => code.Trim().ToLowerInvariant();

    private static DictionaryScope ResolveScopeForQuery(DictionaryScope? requestedScope, long tenantId)
    {
        if (requestedScope.HasValue)
        {
            return requestedScope.Value;
        }

        return tenantId == 0 ? DictionaryScope.System : DictionaryScope.Business;
    }

    private void EnsureScopePermission(DictionaryScope scope)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();
        if (scope == DictionaryScope.System && tenantId != 0)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "仅平台管理员可操作系统字典");
        }
    }

    private void EnsurePlatformTenant(long tenantId)
    {
        if (tenantId != 0)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "仅平台管理员可操作系统字典");
        }
    }

    private async Task InvalidateCacheAsync(DictionaryGroup group, CancellationToken cancellationToken)
    {
        await cache.RemoveAsync(group.TenantId, group.Code, cancellationToken);
        if (group.Scope == DictionaryScope.Business)
        {
            return;
        }

        // 系统参数更新需要逐租户重新合并，由调用方在下一次请求时重新加载
    }

    private async Task<IReadOnlyList<DictionaryItemDto>> GetOrLoadCacheAsync(long tenantId, string code, CancellationToken cancellationToken)
    {
        // 1. 先查缓存
        var cached = await cache.GetAsync(tenantId, code, cancellationToken);
        if (cached != null)
        {
            return cached;
        }

        // 2. 从仓储加载并写入缓存
        var entities = await repository.GetItemsByCodesAsync(new[] { code }, tenantId, includeSystem: false, cancellationToken);
        var items = entities
            .Where(item => item.IsEnabled && (item.Group?.IsEnabled ?? true))
            .Select(MapItem)
            .OrderBy(item => item.SortOrder)
            .ToList();

        await cache.SetAsync(tenantId, code, items, cancellationToken);
        return items;
    }

    private static IReadOnlyList<DictionaryItemDto> MergeItems(IReadOnlyList<DictionaryItemDto> systemItems, IReadOnlyList<DictionaryItemDto> tenantItems)
    {
        if (tenantItems.Count == 0)
        {
            return systemItems;
        }

        if (systemItems.Count == 0)
        {
            return tenantItems;
        }

        return systemItems.Concat(tenantItems)
            .OrderBy(item => item.SortOrder)
            .ToList();
    }

    private static DictionaryGroupDto MapGroup(DictionaryGroup group, bool includeItems, IReadOnlyList<DictionaryItemDto>? items = null)
    {
        return new DictionaryGroupDto
        {
            Id = group.Id,
            Code = group.Code,
            Name = group.Name,
            Scope = group.Scope,
            Description = group.Description,
            IsEnabled = group.IsEnabled,
            Items = includeItems ? items ?? group.Items.Select(MapItem).ToList() : Array.Empty<DictionaryItemDto>()
        };
    }

    private static DictionaryItemDto MapItem(DictionaryItem item)
        => new()
        {
            Id = item.Id,
            GroupId = item.GroupId,
            Key = item.Key,
            Value = item.Value,
            IsDefault = item.IsDefault,
            IsEnabled = item.IsEnabled,
            SortOrder = item.SortOrder,
            Description = item.Description
        };
}
