using System.Security.Cryptography;
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.Domain.Dictionary.ValueObjects;
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 DictionaryCommandService(
    IDictionaryGroupRepository groupRepository,
    IDictionaryItemRepository itemRepository,
    IDictionaryHybridCache cache,
    ITenantProvider tenantProvider,
    ILogger<DictionaryCommandService> logger)
{
    /// <summary>
    /// 创建字典分组。
    /// </summary>
    public async Task<DictionaryGroupDto> CreateGroupAsync(CreateDictionaryGroupRequest request, CancellationToken cancellationToken = default)
    {
        var targetTenantId = ResolveTargetTenant(request.Scope);
        var code = new DictionaryCode(request.Code);

        var existing = await groupRepository.GetByCodeAsync(targetTenantId, code, cancellationToken);
        if (existing != null)
        {
            throw new BusinessException(ErrorCodes.Conflict, $"字典分组编码 {code.Value} 已存在");
        }

        var group = new DictionaryGroup
        {
            TenantId = targetTenantId,
            Code = code,
            Name = request.Name.Trim(),
            Scope = request.Scope,
            AllowOverride = request.AllowOverride,
            Description = request.Description?.Trim(),
            IsEnabled = true,
            RowVersion = RandomNumberGenerator.GetBytes(16)
        };

        await groupRepository.AddAsync(group, cancellationToken);
        await groupRepository.SaveChangesAsync(cancellationToken);
        await cache.InvalidateAsync(
            DictionaryCacheKeys.BuildGroupPrefix(targetTenantId),
            CacheInvalidationOperation.Create,
            cancellationToken);

        logger.LogInformation("创建字典分组 {GroupCode}", group.Code);
        return DictionaryMapper.ToGroupDto(group);
    }

    /// <summary>
    /// 更新字典分组。
    /// </summary>
    public async Task<DictionaryGroupDto> UpdateGroupAsync(long groupId, UpdateDictionaryGroupRequest request, CancellationToken cancellationToken = default)
    {
        var group = await RequireGroupAsync(groupId, cancellationToken);
        EnsureGroupAccess(group);

        EnsureRowVersion(request.RowVersion, group.RowVersion, "字典分组");

        group.Name = request.Name.Trim();
        group.Description = request.Description?.Trim();
        group.IsEnabled = request.IsEnabled;
        group.AllowOverride = request.AllowOverride;
        group.RowVersion = RandomNumberGenerator.GetBytes(16);

        try
        {
            await groupRepository.SaveChangesAsync(cancellationToken);
        }
        catch (Exception exception) when (IsConcurrencyException(exception))
        {
            throw new BusinessException(ErrorCodes.Conflict, "字典分组已被修改，请刷新后重试");
        }

        await InvalidateGroupCacheAsync(group, CacheInvalidationOperation.Update, cancellationToken);
        logger.LogInformation("更新字典分组 {GroupId}", group.Id);
        return DictionaryMapper.ToGroupDto(group);
    }

    /// <summary>
    /// 删除字典分组。
    /// </summary>
    public async Task<bool> DeleteGroupAsync(long groupId, CancellationToken cancellationToken = default)
    {
        var group = await groupRepository.GetByIdAsync(groupId, cancellationToken);
        if (group == null)
        {
            return false;
        }

        EnsureGroupAccess(group);

        var items = await itemRepository.GetByGroupIdAsync(group.TenantId, group.Id, cancellationToken);
        foreach (var item in items)
        {
            await itemRepository.RemoveAsync(item, cancellationToken);
        }

        await groupRepository.RemoveAsync(group, cancellationToken);
        await groupRepository.SaveChangesAsync(cancellationToken);
        await InvalidateGroupCacheAsync(group, CacheInvalidationOperation.Delete, cancellationToken);

        logger.LogInformation("删除字典分组 {GroupId}", group.Id);
        return true;
    }

    /// <summary>
    /// 创建字典项。
    /// </summary>
    public async Task<DictionaryItemDto> CreateItemAsync(CreateDictionaryItemRequest request, CancellationToken cancellationToken = default)
    {
        var group = await RequireGroupAsync(request.GroupId, cancellationToken);
        EnsureGroupAccess(group);

        var items = await itemRepository.GetByGroupIdAsync(group.TenantId, group.Id, cancellationToken);
        var normalizedKey = request.Key.Trim();
        if (items.Any(item => string.Equals(item.Key, normalizedKey, StringComparison.OrdinalIgnoreCase)))
        {
            throw new BusinessException(ErrorCodes.Conflict, $"字典项键 {normalizedKey} 已存在");
        }

        var sortOrder = request.SortOrder;
        if (sortOrder <= 0)
        {
            sortOrder = items.Count == 0 ? 10 : items.Max(item => item.SortOrder) + 10;
        }

        var item = new DictionaryItem
        {
            TenantId = group.TenantId,
            GroupId = group.Id,
            Key = normalizedKey,
            Value = DictionaryValueConverter.Serialize(request.Value),
            Description = request.Description?.Trim(),
            SortOrder = sortOrder,
            IsDefault = request.IsDefault,
            IsEnabled = request.IsEnabled,
            RowVersion = RandomNumberGenerator.GetBytes(16)
        };

        await itemRepository.AddAsync(item, cancellationToken);
        await groupRepository.SaveChangesAsync(cancellationToken);
        await InvalidateItemCacheAsync(group, CacheInvalidationOperation.Create, cancellationToken);

        logger.LogInformation("新增字典项 {ItemId}", item.Id);
        return DictionaryMapper.ToItemDto(item);
    }

    /// <summary>
    /// 更新字典项。
    /// </summary>
    public async Task<DictionaryItemDto> UpdateItemAsync(long itemId, UpdateDictionaryItemRequest request, CancellationToken cancellationToken = default)
    {
        var item = await RequireItemAsync(itemId, cancellationToken);
        var group = await RequireGroupAsync(item.GroupId, cancellationToken);
        EnsureGroupAccess(group);

        EnsureRowVersion(request.RowVersion, item.RowVersion, "字典项");

        var normalizedKey = request.Key.Trim();
        if (!string.Equals(item.Key, normalizedKey, StringComparison.OrdinalIgnoreCase))
        {
            var items = await itemRepository.GetByGroupIdAsync(group.TenantId, group.Id, cancellationToken);
            if (items.Any(existing => existing.Id != item.Id && string.Equals(existing.Key, normalizedKey, StringComparison.OrdinalIgnoreCase)))
            {
                throw new BusinessException(ErrorCodes.Conflict, $"字典项键 {normalizedKey} 已存在");
            }
        }

        item.Key = normalizedKey;
        item.Value = DictionaryValueConverter.Serialize(request.Value);
        item.Description = request.Description?.Trim();
        item.SortOrder = request.SortOrder;
        item.IsDefault = request.IsDefault;
        item.IsEnabled = request.IsEnabled;
        item.RowVersion = RandomNumberGenerator.GetBytes(16);

        try
        {
            await groupRepository.SaveChangesAsync(cancellationToken);
        }
        catch (Exception exception) when (IsConcurrencyException(exception))
        {
            throw new BusinessException(ErrorCodes.Conflict, "字典项已被修改，请刷新后重试");
        }

        await InvalidateItemCacheAsync(group, CacheInvalidationOperation.Update, cancellationToken);
        logger.LogInformation("更新字典项 {ItemId}", item.Id);
        return DictionaryMapper.ToItemDto(item);
    }

    /// <summary>
    /// 删除字典项。
    /// </summary>
    public async Task<bool> DeleteItemAsync(long itemId, CancellationToken cancellationToken = default)
    {
        var item = await itemRepository.GetByIdAsync(itemId, cancellationToken);
        if (item == null)
        {
            return false;
        }

        var group = await RequireGroupAsync(item.GroupId, cancellationToken);
        EnsureGroupAccess(group);

        await itemRepository.RemoveAsync(item, cancellationToken);
        await groupRepository.SaveChangesAsync(cancellationToken);
        await InvalidateItemCacheAsync(group, CacheInvalidationOperation.Delete, cancellationToken);

        logger.LogInformation("删除字典项 {ItemId}", item.Id);
        return true;
    }

    private long ResolveTargetTenant(DictionaryScope scope)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();
        if (scope == DictionaryScope.System)
        {
            // 1. (空行后) 租户端禁止写入系统字典
            if (tenantId != 0)
            {
                throw new BusinessException(ErrorCodes.Forbidden, "租户端不允许创建系统字典");
            }

            return 0;
        }

        // 2. (空行后) 业务字典必须在租户上下文中创建
        if (tenantId == 0)
        {
            throw new BusinessException(ErrorCodes.BadRequest, "业务字典必须在租户上下文中创建");
        }

        return tenantId;
    }

    private void EnsureGroupAccess(DictionaryGroup group)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();

        // 1. (空行后) 租户端不允许操作系统字典
        if (group.Scope == DictionaryScope.System && tenantId != 0)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "租户端不允许操作系统字典");
        }

        // 2. (空行后) 业务字典必须属于当前租户
        if (group.Scope == DictionaryScope.Business && tenantId != group.TenantId)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "无权操作其他租户字典");
        }
    }

    private static void EnsureRowVersion(byte[]? requestVersion, byte[] entityVersion, string resourceName)
    {
        if (requestVersion == null || requestVersion.Length == 0)
        {
            throw new BusinessException(ErrorCodes.ValidationFailed, "RowVersion 不能为空");
        }

        if (!requestVersion.SequenceEqual(entityVersion))
        {
            throw new BusinessException(ErrorCodes.Conflict, $"{resourceName}已被修改，请刷新后重试");
        }
    }

    private async Task<DictionaryGroup> RequireGroupAsync(long groupId, CancellationToken cancellationToken)
    {
        var group = await groupRepository.GetByIdAsync(groupId, cancellationToken);
        if (group == null)
        {
            throw new BusinessException(ErrorCodes.NotFound, "字典分组不存在");
        }

        return group;
    }

    private async Task<DictionaryItem> RequireItemAsync(long itemId, CancellationToken cancellationToken)
    {
        var item = await itemRepository.GetByIdAsync(itemId, cancellationToken);
        if (item == null)
        {
            throw new BusinessException(ErrorCodes.NotFound, "字典项不存在");
        }

        return item;
    }

    private Task InvalidateGroupCacheAsync(
        DictionaryGroup group,
        CacheInvalidationOperation operation,
        CancellationToken cancellationToken)
    {
        var tasks = new List<Task>
        {
            cache.InvalidateAsync(DictionaryCacheKeys.BuildGroupPrefix(group.TenantId), operation, cancellationToken),
            cache.InvalidateAsync(DictionaryCacheKeys.BuildItemKey(group.Id), operation, cancellationToken),
            cache.InvalidateAsync(DictionaryCacheKeys.BuildDictionaryKey(group.TenantId, group.Code), operation, cancellationToken)
        };

        if (group.Scope == DictionaryScope.System)
        {
            tasks.Add(cache.InvalidateAsync(DictionaryCacheKeys.DictionaryPrefix, operation, cancellationToken));
        }

        return Task.WhenAll(tasks);
    }

    private Task InvalidateItemCacheAsync(
        DictionaryGroup group,
        CacheInvalidationOperation operation,
        CancellationToken cancellationToken)
    {
        var tasks = new List<Task>
        {
            cache.InvalidateAsync(DictionaryCacheKeys.BuildItemKey(group.Id), operation, cancellationToken),
            cache.InvalidateAsync(DictionaryCacheKeys.BuildDictionaryKey(group.TenantId, group.Code), operation, cancellationToken)
        };

        if (group.Scope == DictionaryScope.System)
        {
            tasks.Add(cache.InvalidateAsync(DictionaryCacheKeys.DictionaryPrefix, operation, cancellationToken));
        }

        return Task.WhenAll(tasks);
    }

    private static bool IsConcurrencyException(Exception exception)
        => string.Equals(exception.GetType().Name, "DbUpdateConcurrencyException", StringComparison.Ordinal);
}
