using CsvHelper;
using CsvHelper.Configuration;
using System.Globalization;
using System.Text;
using TakeoutSaaS.Application.Dictionary.Abstractions;
using TakeoutSaaS.Application.Dictionary.Models;

namespace TakeoutSaaS.Infrastructure.Dictionary.ImportExport;

/// <summary>
/// CSV 字典导入解析器。
/// </summary>
public sealed class CsvDictionaryParser : ICsvDictionaryParser
{
    private static readonly CsvConfiguration CsvConfiguration = new(CultureInfo.InvariantCulture)
    {
        HasHeaderRecord = true,
        MissingFieldFound = null,
        BadDataFound = null,
        DetectColumnCountChanges = false,
        TrimOptions = TrimOptions.Trim,
        PrepareHeaderForMatch = args => args.Header?.Trim().ToLowerInvariant() ?? string.Empty
    };

    /// <inheritdoc />
    public async Task<IReadOnlyList<DictionaryImportRow>> ParseAsync(Stream stream, CancellationToken cancellationToken = default)
    {
        ArgumentNullException.ThrowIfNull(stream);

        if (stream.CanSeek)
        {
            stream.Position = 0;
        }

        var rows = new List<DictionaryImportRow>();
        using var reader = new StreamReader(stream, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false), detectEncodingFromByteOrderMarks: true, leaveOpen: true);
        using var csv = new CsvReader(reader, CsvConfiguration);

        if (!await csv.ReadAsync() || !csv.ReadHeader())
        {
            return rows;
        }

        while (await csv.ReadAsync())
        {
            cancellationToken.ThrowIfCancellationRequested();

            var rowNumber = csv.Context?.Parser?.Row ?? 0;
            rows.Add(new DictionaryImportRow
            {
                RowNumber = rowNumber,
                Code = ReadString(csv, "code"),
                Key = ReadString(csv, "key"),
                Value = ReadString(csv, "value"),
                SortOrder = ReadInt(csv, "sortorder"),
                IsEnabled = ReadBool(csv, "isenabled"),
                Description = ReadString(csv, "description"),
                Source = ReadString(csv, "source")
            });
        }

        return rows;
    }

    private static string? ReadString(CsvReader csv, string name)
    {
        return csv.TryGetField(name, out string? value)
            ? string.IsNullOrWhiteSpace(value) ? null : value
            : null;
    }

    private static int? ReadInt(CsvReader csv, string name)
    {
        if (csv.TryGetField(name, out string? value) && int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var number))
        {
            return number;
        }

        return null;
    }

    private static bool? ReadBool(CsvReader csv, string name)
    {
        if (csv.TryGetField(name, out string? value) && bool.TryParse(value, out var flag))
        {
            return flag;
        }

        return null;
    }
}
