using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
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.Security;
using TakeoutSaaS.Shared.Abstractions.Tenancy;
using Microsoft.Extensions.Logging;

namespace TakeoutSaaS.Application.Dictionary.Services;

/// <summary>
/// 字典导入导出服务。
/// </summary>
public sealed class DictionaryImportExportService(
    ICsvDictionaryParser csvParser,
    IJsonDictionaryParser jsonParser,
    IDictionaryGroupRepository groupRepository,
    IDictionaryItemRepository itemRepository,
    IDictionaryImportLogRepository importLogRepository,
    IDictionaryHybridCache cache,
    ITenantProvider tenantProvider,
    ICurrentUserAccessor currentUser,
    ILogger<DictionaryImportExportService> logger)
{
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);

    /// <summary>
    /// 导出 CSV。
    /// </summary>
    public async Task ExportToCsvAsync(long groupId, Stream output, CancellationToken cancellationToken = default)
    {
        var group = await RequireGroupAsync(groupId, cancellationToken);
        EnsureGroupReadable(group);

        var items = await ResolveExportItemsAsync(group, cancellationToken);
        await WriteCsvAsync(group, items, output, cancellationToken);
    }

    /// <summary>
    /// 导出 JSON。
    /// </summary>
    public async Task ExportToJsonAsync(long groupId, Stream output, CancellationToken cancellationToken = default)
    {
        var group = await RequireGroupAsync(groupId, cancellationToken);
        EnsureGroupReadable(group);

        var items = await ResolveExportItemsAsync(group, cancellationToken);
        var payload = items.Select(item => new DictionaryExportRow
        {
            Code = group.Code.Value,
            Key = item.Key,
            Value = item.Value,
            SortOrder = item.SortOrder,
            IsEnabled = item.IsEnabled,
            Description = item.Description,
            Source = item.Source
        });

        await JsonSerializer.SerializeAsync(output, payload, JsonOptions, cancellationToken);
    }

    /// <summary>
    /// 导入 CSV。
    /// </summary>
    public async Task<DictionaryImportResultDto> ImportFromCsvAsync(DictionaryImportRequest request, CancellationToken cancellationToken = default)
    {
        var rows = await csvParser.ParseAsync(request.FileStream, cancellationToken);
        return await ImportAsync(request, rows, "CSV", cancellationToken);
    }

    /// <summary>
    /// 导入 JSON。
    /// </summary>
    public async Task<DictionaryImportResultDto> ImportFromJsonAsync(DictionaryImportRequest request, CancellationToken cancellationToken = default)
    {
        var rows = await jsonParser.ParseAsync(request.FileStream, cancellationToken);
        return await ImportAsync(request, rows, "JSON", cancellationToken);
    }

    private async Task<DictionaryImportResultDto> ImportAsync(
        DictionaryImportRequest request,
        IReadOnlyList<DictionaryImportRow> rows,
        string format,
        CancellationToken cancellationToken)
    {
        var stopwatch = Stopwatch.StartNew();
        var group = await RequireGroupAsync(request.GroupId, cancellationToken);
        EnsureGroupWritable(group);

        var errors = new List<DictionaryImportResultDto.ImportError>();
        var validRows = new List<NormalizedRow>(rows.Count);
        var hasFatalErrors = false;

        foreach (var row in rows)
        {
            if (!TryNormalizeRow(group, row, errors, out var normalized))
            {
                hasFatalErrors = true;
                continue;
            }

            validRows.Add(normalized);
        }

        if (hasFatalErrors)
        {
            var failed = BuildResult(0, 0, errors, stopwatch.Elapsed);
            await RecordImportLogAsync(request, group, format, failed, stopwatch.Elapsed, cancellationToken);
            return failed;
        }

        var existingItems = await itemRepository.GetByGroupIdAsync(group.TenantId, group.Id, cancellationToken);
        var existingMap = existingItems.ToDictionary(item => item.Key, StringComparer.OrdinalIgnoreCase);
        var seenKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase);

        var successCount = 0;
        var skipCount = 0;
        var nextSortOrder = existingItems.Count == 0 ? 0 : existingItems.Max(item => item.SortOrder);

        foreach (var row in validRows)
        {
            cancellationToken.ThrowIfCancellationRequested();

            if (!seenKeys.Add(row.Key))
            {
                skipCount++;
                errors.Add(CreateError(row.RowNumber, "key", $"字典项键 {row.Key} 在导入文件中重复"));
                continue;
            }

            if (existingMap.TryGetValue(row.Key, out var existing))
            {
                if (request.ConflictMode != ConflictResolutionMode.Overwrite)
                {
                    skipCount++;
                    errors.Add(CreateError(row.RowNumber, "key", $"字典项键 {row.Key} 已存在"));
                    continue;
                }

                ApplyUpdate(existing, row, existing.SortOrder, overwriteSort: row.SortOrder.HasValue);
                await itemRepository.UpdateAsync(existing, cancellationToken);
                successCount++;
                continue;
            }

            var sortOrder = row.SortOrder ?? 0;
            if (!row.SortOrder.HasValue)
            {
                nextSortOrder = nextSortOrder == 0 ? 10 : nextSortOrder + 10;
                sortOrder = nextSortOrder;
            }

            var item = new DictionaryItem
            {
                TenantId = group.TenantId,
                GroupId = group.Id,
                Key = row.Key,
                Value = row.ValueJson,
                SortOrder = sortOrder,
                IsEnabled = row.IsEnabled ?? true,
                IsDefault = false,
                Description = row.Description,
                RowVersion = RandomNumberGenerator.GetBytes(16)
            };

            await itemRepository.AddAsync(item, cancellationToken);
            existingMap[item.Key] = item;
            successCount++;
        }

        await itemRepository.SaveChangesAsync(cancellationToken);
        await InvalidateGroupCacheAsync(group, cancellationToken);

        var result = BuildResult(successCount, skipCount, errors, stopwatch.Elapsed);
        await RecordImportLogAsync(request, group, format, result, stopwatch.Elapsed, cancellationToken);
        return result;
    }

    private static void ApplyUpdate(DictionaryItem item, NormalizedRow row, int defaultSortOrder, bool overwriteSort)
    {
        item.Key = row.Key;
        item.Value = row.ValueJson;
        if (overwriteSort)
        {
            item.SortOrder = row.SortOrder ?? defaultSortOrder;
        }

        if (row.IsEnabled.HasValue)
        {
            item.IsEnabled = row.IsEnabled.Value;
        }

        if (!string.IsNullOrWhiteSpace(row.Description))
        {
            item.Description = row.Description;
        }

    }

    private async Task<IReadOnlyList<DictionaryItemDto>> ResolveExportItemsAsync(DictionaryGroup group, CancellationToken cancellationToken)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();

        if (group.Scope == DictionaryScope.System && tenantId != 0)
        {
            var mergedItems = await itemRepository.GetMergedItemsAsync(tenantId, group.Id, includeOverrides: true, cancellationToken);
            return mergedItems.Select(DictionaryMapper.ToItemDto).ToList();
        }

        var items = await itemRepository.GetByGroupIdAsync(group.TenantId, group.Id, cancellationToken);
        return items.Select(DictionaryMapper.ToItemDto).ToList();
    }

    private static async Task WriteCsvAsync(
        DictionaryGroup group,
        IReadOnlyList<DictionaryItemDto> items,
        Stream output,
        CancellationToken cancellationToken)
    {
        await using var writer = new StreamWriter(output, new UTF8Encoding(encoderShouldEmitUTF8Identifier: true), leaveOpen: true);

        await writer.WriteLineAsync("code,key,value,sortOrder,isEnabled,description,source");
        foreach (var item in items)
        {
            cancellationToken.ThrowIfCancellationRequested();

            var valueJson = JsonSerializer.Serialize(item.Value, JsonOptions);
            var row = new[]
            {
                group.Code.Value,
                item.Key,
                valueJson,
                item.SortOrder.ToString(),
                item.IsEnabled ? "true" : "false",
                item.Description ?? string.Empty,
                item.Source
            };

            await writer.WriteLineAsync(ToCsvRow(row));
        }

        await writer.FlushAsync(cancellationToken);
    }

    private static string ToCsvRow(IEnumerable<string> fields)
        => string.Join(",", fields.Select(EscapeCsvField));

    private static string EscapeCsvField(string value)
    {
        if (value.Contains('"', StringComparison.Ordinal))
        {
            value = value.Replace("\"", "\"\"");
        }

        if (value.Contains(',', StringComparison.Ordinal) ||
            value.Contains('\n', StringComparison.Ordinal) ||
            value.Contains('\r', StringComparison.Ordinal) ||
            value.Contains('"', StringComparison.Ordinal))
        {
            return $"\"{value}\"";
        }

        return value;
    }

    private bool TryNormalizeRow(
        DictionaryGroup group,
        DictionaryImportRow row,
        ICollection<DictionaryImportResultDto.ImportError> errors,
        out NormalizedRow normalized)
    {
        normalized = default;

        if (!string.IsNullOrWhiteSpace(row.Code) &&
            !string.Equals(row.Code.Trim(), group.Code.Value, StringComparison.OrdinalIgnoreCase))
        {
            errors.Add(CreateError(row.RowNumber, "code", "字典分组编码不匹配"));
            return false;
        }

        if (string.IsNullOrWhiteSpace(row.Key))
        {
            errors.Add(CreateError(row.RowNumber, "key", "字典项键不能为空"));
            return false;
        }

        var key = row.Key.Trim();
        if (key.Length > 128)
        {
            errors.Add(CreateError(row.RowNumber, "key", "字典项键长度不能超过 128"));
            return false;
        }

        if (string.IsNullOrWhiteSpace(row.Value))
        {
            errors.Add(CreateError(row.RowNumber, "value", "字典项值不能为空"));
            return false;
        }

        string valueJson;
        try
        {
            var i18n = I18nValue.FromJson(row.Value);
            valueJson = i18n.ToJson();
        }
        catch (ArgumentException)
        {
            errors.Add(CreateError(row.RowNumber, "value", "字典项值必须为合法的多语言 JSON"));
            return false;
        }
        catch (JsonException)
        {
            errors.Add(CreateError(row.RowNumber, "value", "字典项值必须为合法的多语言 JSON"));
            return false;
        }

        if (row.SortOrder.HasValue && row.SortOrder.Value < 0)
        {
            errors.Add(CreateError(row.RowNumber, "sortOrder", "排序值不能小于 0"));
            return false;
        }

        normalized = new NormalizedRow
        {
            RowNumber = row.RowNumber,
            Key = key,
            ValueJson = valueJson,
            SortOrder = row.SortOrder,
            IsEnabled = row.IsEnabled,
            Description = row.Description?.Trim()
        };

        return true;
    }

    private static DictionaryImportResultDto BuildResult(
        int successCount,
        int skipCount,
        IReadOnlyList<DictionaryImportResultDto.ImportError> errors,
        TimeSpan duration)
    {
        return new DictionaryImportResultDto
        {
            SuccessCount = successCount,
            SkipCount = skipCount,
            ErrorCount = errors.Count,
            Errors = errors.ToArray(),
            Duration = duration
        };
    }

    private async Task RecordImportLogAsync(
        DictionaryImportRequest request,
        DictionaryGroup group,
        string format,
        DictionaryImportResultDto result,
        TimeSpan duration,
        CancellationToken cancellationToken)
    {
        try
        {
            var log = new DictionaryImportLog
            {
                TenantId = group.TenantId,
                OperatorId = currentUser.UserId,
                DictionaryGroupCode = group.Code.Value,
                FileName = request.FileName,
                FileSize = request.FileSize,
                Format = format,
                ConflictMode = request.ConflictMode,
                SuccessCount = result.SuccessCount,
                SkipCount = result.SkipCount,
                ErrorDetails = result.Errors.Count == 0 ? null : JsonSerializer.Serialize(result.Errors, JsonOptions),
                ProcessedAt = DateTime.UtcNow,
                Duration = duration
            };

            await importLogRepository.AddAsync(log, cancellationToken);
            await importLogRepository.SaveChangesAsync(cancellationToken);
        }
        catch (Exception exception)
        {
            logger.LogWarning(exception, "记录字典导入日志失败");
        }
    }

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

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

        await Task.WhenAll(tasks);
    }

    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 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 void EnsureGroupReadable(DictionaryGroup group)
    {
        var tenantId = tenantProvider.GetCurrentTenantId();
        if (tenantId != 0 && group.Scope == DictionaryScope.Business && group.TenantId != tenantId)
        {
            throw new BusinessException(ErrorCodes.Forbidden, "无权访问其他租户字典");
        }
    }

    private void EnsureGroupWritable(DictionaryGroup group)
    {
        EnsureGroupAccess(group);
    }

    private static DictionaryImportResultDto.ImportError CreateError(int rowNumber, string field, string message)
        => new()
        {
            RowNumber = rowNumber,
            Field = field,
            Message = message
        };

    private sealed class DictionaryExportRow
    {
        public string Code { get; init; } = string.Empty;
        public string Key { get; init; } = string.Empty;
        public Dictionary<string, string> Value { get; init; } = new(StringComparer.OrdinalIgnoreCase);
        public int SortOrder { get; init; }
        public bool IsEnabled { get; init; }
        public string? Description { get; init; }
        public string Source { get; init; } = "system";
    }

    private readonly struct NormalizedRow
    {
        public int RowNumber { get; init; }
        public string Key { get; init; }
        public string ValueJson { get; init; }
        public int? SortOrder { get; init; }
        public bool? IsEnabled { get; init; }
        public string? Description { get; init; }
    }
}
