using DD.Persistence.Models;
using DD.Persistence.Models.Configurations;
using DD.Persistence.Models.Enumerations;
using DD.Persistence.Repositories;
using DD.Persistence.Services.Interfaces;
using System.Globalization;
using System.Text.Json;
using System.Text.Json.Serialization;

namespace DD.Persistence.Services;
public class WitsDataService : IWitsDataService
{
    private readonly IParameterRepository witsDataRepository;

    private readonly WitsInfo[] witsInfo;

    private const int multiplier = 1000;
    private const string witsConfigPath = "DD.Persistence.Services.Config.WitsConfig.json";

    public WitsDataService(IParameterRepository witsDataRepository)
    {
        this.witsDataRepository = witsDataRepository;

        this.witsInfo = GetWitsInfo();
    }

    public Task<DatesRangeDto> GetDatesRangeAsync(Guid idDiscriminator, CancellationToken token)
    {
        var result = witsDataRepository.GetDatesRangeAsync(idDiscriminator, token);

        return result;
    }

    public async Task<IEnumerable<WitsDataDto>> GetPart(Guid idDiscriminator, DateTimeOffset dateBegin, int take, CancellationToken token)
    {
        var dtos = await witsDataRepository.GetPart(idDiscriminator, dateBegin, take, token);

        var result = AdaptToWitsData(dtos);

        return result;
    }

    public async Task<IEnumerable<WitsDataDto>> GetValuesForGraph(Guid discriminatorId, DateTimeOffset dateFrom, DateTimeOffset dateTo,
        int approxPointsCount, CancellationToken token)
    {
        var intervalSec = (dateTo - dateFrom).TotalSeconds;

        int? ratio = null;
        if (intervalSec > 2 * approxPointsCount)
        {
            ratio = (int)intervalSec / approxPointsCount;
        }

        var dtos = await witsDataRepository.GetValuesForGraph(discriminatorId, dateFrom, dateTo, approxPointsCount, ratio, token);

        var result = AdaptToWitsData(dtos);

        return result;
    }

    public async Task<int> AddRange(IEnumerable<WitsDataDto> dtos, CancellationToken token)
    {
        var parameterDtos = dtos.SelectMany(e => e.Values.Select(t => new ParameterDto()
        {
            DiscriminatorId = e.DiscriminatorId,
            ParameterId = EncodeId(t.RecordId, t.ItemId),
            Value = t.Value.ToString()!,
            Timestamp = e.Timestamped
        }));
        var result = await witsDataRepository.AddRange(parameterDtos, token);

        return result;
    }

    private int EncodeId(int recordId, int itemId)
    {
        var resultId = multiplier * recordId + itemId;
        return resultId;
    }

    private int DecodeRecordId(int id)
    {
        var resultId = id / multiplier;

        return resultId;
    }

    private int DecodeItemId(int id)
    {
        var resultId = id % multiplier;

        return resultId;
    }

    private IEnumerable<WitsDataDto> AdaptToWitsData(IEnumerable<ParameterDto> dtos)
    {
        var result = new List<WitsDataDto>();
        var witsGroup = dtos
            .GroupBy(e => new { e.DiscriminatorId, e.Timestamp });
        foreach (var witsInGroup in witsGroup)
        {
            var witsDataDto = new WitsDataDto()
            {
                DiscriminatorId = witsInGroup.Key.DiscriminatorId,
                Timestamped = witsInGroup.Key.Timestamp
            };

            witsDataDto.Values = witsInGroup.Select(e =>
            {
                var recordId = DecodeRecordId(e.ParameterId);
                var itemId = DecodeItemId(e.ParameterId);

                return new WitsValueDto()
                {
                    RecordId = recordId,
                    ItemId = itemId,
                    Value = ConvertValue(recordId, itemId, e.Value)
                };
            });

            result.Add(witsDataDto);
        }

        return result;
    }

    private object ConvertValue(int recordId, int itemId, string value)
    {
        var witsType = witsInfo.FirstOrDefault(e => e.ItemId == itemId
            && e.RecordId == recordId)?.ValueType;

        switch (witsType)
        {
            default:
                {
                    return value;
                }
            case WitsType.S:
                {
                    var result = Int16.Parse(value);

                    return result;
                }
            case WitsType.L:
                {
                    var result = Int32.Parse(value);

                    return result;
                }
            case WitsType.F:
                {
                    var result = float.Parse(value, CultureInfo.InvariantCulture);

                    return result;
                }
        }
    }

    private WitsInfo[] GetWitsInfo()
    {
        var stream = System.Reflection.Assembly.GetExecutingAssembly()
            .GetManifestResourceStream(witsConfigPath);
        var options = new JsonSerializerOptions
        {
            Converters =
            {
                new JsonStringEnumConverter(JsonNamingPolicy.CamelCase)
            },
        };
        var records = JsonSerializer.Deserialize<WitsInfo[]>(stream!, options) ?? [];
        return records;
    }
}