using System.Text.Json;
using System.Text.Json.Serialization;

namespace YMhut.Box.Core.Feedback;

public sealed class FeedbackRecordStore(string? storagePath = null) : IFeedbackRecordStore
{
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web)
    {
        WriteIndented = true,
        Converters = { new JsonStringEnumConverter() }
    };

    private readonly SemaphoreSlim _gate = new(1, 1);
    private readonly string _storagePath = string.IsNullOrWhiteSpace(storagePath)
        ? DefaultStoragePath()
        : storagePath;

    public static string DefaultStoragePath()
    {
        return Path.Combine(FeedbackPackageService.FeedbackPackagesRoot(), "feedback-records.json");
    }

    public async Task<IReadOnlyList<FeedbackRecord>> ReadAsync(CancellationToken cancellationToken = default)
    {
        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            return await ReadUnsafeAsync(cancellationToken).ConfigureAwait(false);
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task<FeedbackRecord?> FindAsync(string feedbackCode, CancellationToken cancellationToken = default)
    {
        var normalized = FeedbackCode.Normalize(feedbackCode);
        if (!FeedbackCode.IsValid(normalized))
        {
            return null;
        }

        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            var records = await ReadUnsafeAsync(cancellationToken).ConfigureAwait(false);
            return records.FirstOrDefault(record => string.Equals(record.Code, normalized, StringComparison.OrdinalIgnoreCase));
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task<FeedbackRecord> UpsertAsync(FeedbackRecord record, CancellationToken cancellationToken = default)
    {
        var normalized = FeedbackCode.Normalize(record.Code);
        if (!FeedbackCode.IsValid(normalized))
        {
            throw new InvalidOperationException("反馈编号无效。");
        }

        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            var records = (await ReadUnsafeAsync(cancellationToken).ConfigureAwait(false)).ToList();
            var index = records.FindIndex(item => string.Equals(item.Code, normalized, StringComparison.OrdinalIgnoreCase));
            var existing = index >= 0 ? records[index] : null;
            var merged = Merge(existing, record, normalized);
            if (index >= 0)
            {
                records[index] = merged;
            }
            else
            {
                records.Add(merged);
            }

            await WriteUnsafeAsync(records, cancellationToken).ConfigureAwait(false);
            return merged;
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task RemoveAsync(string feedbackCode, CancellationToken cancellationToken = default)
    {
        var normalized = FeedbackCode.Normalize(feedbackCode);
        if (!FeedbackCode.IsValid(normalized))
        {
            return;
        }

        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            var records = (await ReadUnsafeAsync(cancellationToken).ConfigureAwait(false)).ToList();
            var removed = records.RemoveAll(record => string.Equals(record.Code, normalized, StringComparison.OrdinalIgnoreCase));
            if (removed > 0)
            {
                await WriteUnsafeAsync(records, cancellationToken).ConfigureAwait(false);
            }
        }
        finally
        {
            _gate.Release();
        }
    }

    public async Task<IReadOnlyList<FeedbackRecord>> ArchiveOlderThanAsync(
        TimeSpan activeWindow,
        DateTimeOffset? now = null,
        CancellationToken cancellationToken = default)
    {
        var current = (now ?? DateTimeOffset.UtcNow).ToUniversalTime();
        var threshold = current - activeWindow;

        await _gate.WaitAsync(cancellationToken).ConfigureAwait(false);
        try
        {
            var changed = false;
            var records = (await ReadUnsafeAsync(cancellationToken).ConfigureAwait(false)).ToList();
            for (var index = 0; index < records.Count; index++)
            {
                var record = Normalize(records[index]);
                if (!record.IsArchived && record.CreatedAtUtc.ToUniversalTime() <= threshold)
                {
                    record = record with
                    {
                        State = FeedbackRecordState.Archived,
                        StatusLabel = "已归档",
                        LastMessage = "该反馈记录已超过 10 天，已自动归档到本地历史。",
                        UpdatedAtUtc = current,
                        ArchivedAtUtc = current,
                        IsArchived = true
                    };
                    changed = true;
                }

                records[index] = record;
            }

            if (changed)
            {
                await WriteUnsafeAsync(records, cancellationToken).ConfigureAwait(false);
            }

            return Sort(records);
        }
        finally
        {
            _gate.Release();
        }
    }

    private static FeedbackRecord Merge(FeedbackRecord? existing, FeedbackRecord record, string normalizedCode)
    {
        var normalized = Normalize(record with { Code = normalizedCode });
        if (existing is null)
        {
            return normalized;
        }

        var archived = existing.IsArchived || normalized.IsArchived;
        var archivedAt = normalized.ArchivedAtUtc ?? existing.ArchivedAtUtc;
        return normalized with
        {
            CreatedAtUtc = existing.CreatedAtUtc <= normalized.CreatedAtUtc ? existing.CreatedAtUtc : normalized.CreatedAtUtc,
            ArchivedAtUtc = archivedAt,
            IsArchived = archived
        };
    }

    private static FeedbackRecord Normalize(FeedbackRecord record)
    {
        var now = DateTimeOffset.UtcNow;
        var created = record.CreatedAtUtc == default ? now : record.CreatedAtUtc.ToUniversalTime();
        var updated = record.UpdatedAtUtc == default ? created : record.UpdatedAtUtc.ToUniversalTime();
        var title = string.IsNullOrWhiteSpace(record.Title) ? "未命名反馈" : record.Title.Trim();
        var label = string.IsNullOrWhiteSpace(record.StatusLabel) ? StateLabel(record.State) : record.StatusLabel.Trim();
        var message = string.IsNullOrWhiteSpace(record.LastMessage) ? "暂无更多状态信息。" : record.LastMessage.Trim();

        return record with
        {
            Code = FeedbackCode.Normalize(record.Code),
            Title = title.Length > 160 ? title[..160] : title,
            Type = string.IsNullOrWhiteSpace(record.Type) ? "issue" : record.Type.Trim(),
            Severity = string.IsNullOrWhiteSpace(record.Severity) ? "normal" : record.Severity.Trim(),
            CreatedAtUtc = created,
            UpdatedAtUtc = updated,
            StatusLabel = label.Length > 80 ? label[..80] : label,
            LastMessage = message.Length > 500 ? message[..500] : message,
            FailureCount = Math.Max(0, record.FailureCount),
            PackageBytes = Math.Max(0, record.PackageBytes),
            ServiceStatus = TrimOrNull(record.ServiceStatus, 80),
            PublicReply = TrimOrNull(record.PublicReply, 800),
            ServiceStatusDetail = TrimOrNull(record.ServiceStatusDetail, 500),
            ServiceCategory = TrimOrNull(record.ServiceCategory, 80),
            ServicePriority = TrimOrNull(record.ServicePriority, 80),
            ServiceReceivedAt = TrimOrNull(record.ServiceReceivedAt, 80),
            ServiceUpdatedAt = TrimOrNull(record.ServiceUpdatedAt, 80)
        };
    }

    private static string? TrimOrNull(string? value, int maxLength)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return null;
        }

        var trimmed = value.Trim();
        return trimmed.Length > maxLength ? trimmed[..maxLength] : trimmed;
    }

    private static string StateLabel(FeedbackRecordState state)
    {
        return state switch
        {
            FeedbackRecordState.PackageCreated => "反馈包已生成",
            FeedbackRecordState.PackageFailed => "生成失败",
            FeedbackRecordState.Sending => "发送中",
            FeedbackRecordState.Sent => "发送成功",
            FeedbackRecordState.SendFailed => "发送失败",
            FeedbackRecordState.Querying => "正在获取状态",
            FeedbackRecordState.StatusUpdated => "状态已更新",
            FeedbackRecordState.QueryFailed => "获取失败",
            FeedbackRecordState.Archived => "已归档",
            _ => "未发送"
        };
    }

    private async Task<IReadOnlyList<FeedbackRecord>> ReadUnsafeAsync(CancellationToken cancellationToken)
    {
        if (!File.Exists(_storagePath))
        {
            return [];
        }

        try
        {
            await using var stream = File.OpenRead(_storagePath);
            var records = await JsonSerializer.DeserializeAsync<List<FeedbackRecord>>(stream, JsonOptions, cancellationToken)
                .ConfigureAwait(false);
            return Sort(records?.Select(Normalize) ?? []);
        }
        catch (JsonException)
        {
            return [];
        }
        catch (IOException)
        {
            return [];
        }
    }

    private async Task WriteUnsafeAsync(IReadOnlyList<FeedbackRecord> records, CancellationToken cancellationToken)
    {
        var folder = Path.GetDirectoryName(_storagePath);
        if (!string.IsNullOrWhiteSpace(folder))
        {
            Directory.CreateDirectory(folder);
        }

        var ordered = Sort(records.Select(Normalize));
        var tempPath = _storagePath + ".tmp";
        await using (var stream = File.Create(tempPath))
        {
            await JsonSerializer.SerializeAsync(stream, ordered, JsonOptions, cancellationToken).ConfigureAwait(false);
        }

        File.Move(tempPath, _storagePath, overwrite: true);
    }

    private static IReadOnlyList<FeedbackRecord> Sort(IEnumerable<FeedbackRecord> records)
    {
        return records
            .OrderBy(record => record.IsArchived)
            .ThenByDescending(record => record.UpdatedAtUtc)
            .ThenByDescending(record => record.CreatedAtUtc)
            .ToArray();
    }
}
