using Forwarder.Domain;
using Forwarder.Models;
using Forwarder.Repositories;

using Microsoft.Extensions.Options;

using Serilog;

namespace Forwarder.Services;

/// <summary>
/// Отправляет сообщения.
/// </summary>
/// <param name="options">Опции.</param>
internal class SendJob(
	IOptions<ForwarderOptions> options,
	IServiceProvider serviceProvider
) : BackgroundService
{
	/// <inheritdoc />
	protected override async Task ExecuteAsync(CancellationToken stoppingToken)
	{
		while (!stoppingToken.IsCancellationRequested)
		{
			try
			{
				await ExecuteBatch(stoppingToken);
			}
			catch (Exception ex)
			{
				Log.Error(ex, "Error in ExecuteBatch");
			}

			await Task.Delay(options.Value.SendingIntervalInSeconds * 1000, stoppingToken);
		}
	}

	private async Task ExecuteBatch(CancellationToken stoppingToken)
	{
		using var scope = serviceProvider.CreateScope();
		var provider = scope.ServiceProvider;

		var sendService = provider.GetRequiredService<ISendService>();
		var commonRepository = provider.GetRequiredService<ICommonRepository>();
		var messageRepository = provider.GetRequiredService<IMessageRepository>();
		var assetMapRepository = provider.GetRequiredService<IAssetMapRepository>();

		var messages = await ReadMessages(commonRepository, stoppingToken);
		var maps = await GetMapsByAbonentList(
			assetMapRepository,
			[.. messages.Select(x => x.Abonent).Distinct()],
			stoppingToken);

		if (maps.IsFail)
		{
			Log.Error("GetMapsByAbonentList failed: {Error}", maps.Error);
			return;
		}

		var tasks = new List<Task<Result>>();
		var messageIdListForRemoving = new List<Guid>();

		foreach (var message in messages)
		{
			 if (message.Type == MessageType.StatePing)
        {
            var map = maps.Value.FirstOrDefault(x => x.Abonent == message.Abonent);
            if (map?.AssetId is not null)
            {
                var task = sendService.SendTestMessage(message, map.AssetId.Value, stoppingToken);
                tasks.Add(task);
            }
        }
        else
        {
            // Существующая логика для других типов сообщений
            var map = maps.Value.FirstOrDefault(x => x.Abonent == message.Abonent);
            if (map?.AssetId is not null)
            {
                var task = sendService.Send(message, map.AssetId.Value, stoppingToken);
                tasks.Add(task);
            }
        }
        
        messageIdListForRemoving.Add(message.Id);

			var mapResult = GetMapForMessage(message, maps.Value);
			if (mapResult.IsFail)
			{
				continue;
			}

			tasks.Add(Task.Run(async () =>
			{
				var sendResult = await TrySendMessage(sendService, message, mapResult.Value, stoppingToken);
				if (sendResult.IsFail)
				{
					return Result.Fail(sendResult.Error);
				}

				messageIdListForRemoving.Add(message.Id);

				return Result.Success();
			}));
		}

		await Task.WhenAll(tasks);

		if (tasks.Count != 0)
		{
			_ = await messageRepository.RemoveBatch(messageIdListForRemoving, stoppingToken);
		}
	}

	private static Result<AssetMap> GetMapForMessage(Message message, List<AssetMap> maps)
	{
		var map = maps.Find(x => x.Abonent == message.Abonent);
		return map != null
			? Result<AssetMap>.Success(map)
			: Result<AssetMap>.Fail($"Не удалось найти маппинг для абонента {message.Abonent}");
	}

	private async Task<Result<List<AssetMap>>> GetMapsByAbonentList(IAssetMapRepository assetMapRepository, List<string> abonentList, CancellationToken cancellationToken)
	{
		return await assetMapRepository.GetRequiredMapsAndCreateIsNewAsync(abonentList, cancellationToken);
	}

	private async Task<Result> TrySendMessage(ISendService sendService, Message message, AssetMap map, CancellationToken cancellationToken)
	{
		Log.Information("Попытка отправки сообщения: {Message}", message);

		if (!map.AssetId.HasValue)
		{
			Log.Information("AssetId не определен. Пропуск.");
			return Result.Fail("AssetId is null");
		}

		var sendResult = await sendService.Send(message, map.AssetId.Value, cancellationToken);
		if (sendResult.IsFail)
		{
			Log.Error(sendResult.Error);
		}

		return sendResult;
	}

	private async Task<List<Message>> ReadMessages(ICommonRepository commonRepository, CancellationToken cancellationToken)
	{
		var result = await commonRepository.GetFirstListWhereMapReady(
			options.Value.MessageBulkSize,
			cancellationToken);
		if (result.IsFail)
		{
			Log.Error("GetFirstListWhereMapReady failed: {Error}", result.Error);
			return [];
		}

		return [.. result.Value];
	}
}
