using Microsoft.EntityFrameworkCore;

using Monitoring.Core.Domain.UserLogManagement;
using Monitoring.Core.Domain.UserPrimitives;

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

		await MoveLogs(cancellationToken);

		logger.LogInformation("Done moving user logs");
	}

	private async Task MoveLogs(CancellationToken cancellationToken)
	{
		var count = await contexts.Source.Logs.CountAsync(cancellationToken);

		var userMaps = await contexts.Middleware.Users
			.ToListAsync(cancellationToken);
		var userMapDictionary = userMaps
			.ToDictionary(x => x.From, x => x.To);

		for (var i = 0; i < count; i += Constants.BatchSize)
		{
			await PassLogBatch(i, Constants.BatchSize, userMapDictionary, cancellationToken);
		}
	}

	private async Task PassLogBatch(int startIndex, int count,
		IReadOnlyDictionary<int, UserId> userMapDictionary, CancellationToken cancellationToken)
	{
		logger.LogWarning("Start log batch with index: {startIndex}", startIndex);

		var sourceLogList = await contexts.Source.Logs
			.AsNoTracking()
			.OrderBy(x => x.Id)
			.Skip(startIndex)
			.Take(count)
			.ToListAsync(cancellationToken);
		var sourceLogDictionary = sourceLogList.ToDictionary(x => x.Id);

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

		var eventCodes = await contexts.Destination.EventCodes
			.ToListAsync(cancellationToken);

		var eventList = sourceLogList
			.Select(sourceLog =>
			{
				var userId = userMapDictionary[sourceLog.UserId];
				var assetId = assetMapDictionary[sourceLog.ObjectId];
				var information = UserLogInformation.Create(
					stringCutter.Cut(sourceLog.Comment, UserLogInformation.MaxLength));
				return UserLog.Create(
					userId,
					assetId,
					information,
					timeService.ConvertToUtc(sourceLog.Time)
				);
			})
			.ToList();

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

		try
		{
			logger.LogInformation("Try save user logs");

			await contexts.Destination.UserLogs.AddRangeAsync(eventList, cancellationToken);
			_ = await contexts.Destination.SaveChangesAsync(cancellationToken);

			logger.LogInformation("User logs is saved");

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

			throw;
		}

		logger.LogInformation("Done moving log batch: start = {start}, count = {count}", startIndex, Constants.BatchSize);
	}
}
