using EFCore.BulkExtensions;

using Microsoft.EntityFrameworkCore;

using Monitoring.Core.Domain.EventManagement;
using Monitoring.Core.Domain.Primitives;

using EventId = Monitoring.Core.Domain.EventManagement.EventId;

namespace GeoRitmMover.Services.Implementations.TableMovers;
public class EventTableMover(
	ILogger<EventTableMover> logger,
	IContexts contexts,
	TimeService timeService
) : ITableMover
{
	public async Task Move(CancellationToken cancellationToken)
	{
		logger.LogInformation("Moving events");

		await MoveAlerts(cancellationToken);

		logger.LogInformation("Done moving events");
	}

	private async Task MoveAlerts(CancellationToken cancellationToken)
	{
		var count = await contexts.Source.Alerts.CountAsync(cancellationToken);
		long lastAlertId = 0;

		var codes = await contexts.Destination.EventCodes.ToListAsync(cancellationToken);
		var codeByValue = codes.ToDictionary(x => x.Value.Value, x => x);

		for (var i = 0; i < count; i += Constants.BatchSize)
		{
			lastAlertId = await PassAlertBatch(Constants.BatchSize,
				lastAlertId, codeByValue, cancellationToken);
		}
	}

	private async Task<long> PassAlertBatch(
		int count,
		long lastAlertId,
		Dictionary<int, EventCode> codeByValue,
		CancellationToken cancellationToken)
	{
		logger.LogWarning("Start alert batch with alert id > {lastAlertId}", lastAlertId);

		var sourceAlertList = await contexts.Source.Alerts
			.AsNoTracking()
			.OrderBy(x => x.Id)
			.Where(x => x.Id > lastAlertId)
			.Take(count)
			.Include(x => x.Message)
			.ToListAsync(cancellationToken);
		var sourceAlertDictionary = sourceAlertList.ToDictionary(x => x.Id);

		var sourceObjectIdList = sourceAlertList.Select(x => x.ObjectId).Distinct().ToList();
		var assetMapList = await contexts.Middleware.Assets
			.AsNoTracking()
			.Where(x => sourceObjectIdList.Contains(x.From))
			.ToListAsync(cancellationToken);
		var assetMapDictionary = assetMapList
			.ToDictionary(x => x.From, x => x.To);

		List<Event> eventList = [];
		foreach (var sourceAlert in sourceAlertList)
		{
			var assetId = assetMapDictionary[sourceAlert.ObjectId];
			var code = sourceAlert.Message.Code;

			var areaIndex = new AreaIndex(sourceAlert.Area);
			var zoneIndex = new ZoneIndex(sourceAlert.Zone);
			var eventCodeId = codeByValue[sourceAlert.Message.Code].Id;

			var @event = Event.Create(
				new EventId(Guid.NewGuid()),
				eventCodeId,
				areaIndex,
				zoneIndex,
				assetId,
				timeService.ConvertToUtc(sourceAlert.ReceivedAt),
				timeService.ConvertToUtc(sourceAlert.AlertAt)
			);

			eventList.Add(@event);
		};

		using var transaction = await contexts.Destination.Database
			.BeginTransactionAsync(cancellationToken);

		try
		{
			logger.LogInformation("Try save alerts");

			await contexts.Destination.BulkInsertAsync(eventList, cancellationToken: cancellationToken);
			_ = await contexts.Destination.SaveChangesAsync(cancellationToken);

			logger.LogInformation("Alerts is saved");

			await transaction.CommitAsync(cancellationToken);
		}
		catch (Exception ex)
		{
			logger.LogError(ex, "Error while saving alerts");
			await transaction.RollbackAsync(cancellationToken);

			throw;
		}

		logger.LogInformation("Done moving alert batch");

		return sourceAlertList.Last().Id;
	}
}
