using GeoRitmMover.Mover.Data;

using Microsoft.EntityFrameworkCore;

using Monitoring.Core.Domain;
using Monitoring.Core.Domain.OrganizationManagement;
using Monitoring.Core.Domain.RolePrimitives;
using Monitoring.Core.Domain.UserPrimitives;
using Monitoring.Infrastructure.Abstractions;

namespace GeoRitmMover.Services.Implementations.TableMovers;
public class UserTableMover(
	ILogger<UserTableMover> logger,
	IContexts contexts,
	IPasswordService passwordService
) : ITableMover
{
	public async Task Move(CancellationToken cancellationToken)
	{
		logger.LogInformation("Moving users");

		var count = await contexts.Source.Users.CountAsync(cancellationToken);

		var userMapList = await contexts.Middleware.Users
			.ToListAsync(cancellationToken);
		var userIds = userMapList.Select(u => u.To).ToList();

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

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

	private async Task PassBatch(int startIndex, int count, CancellationToken cancellationToken)
	{
		logger.LogInformation("Start index: {startIndex}", startIndex);

		var sourceUserList = await contexts.Source.Users
			.AsNoTracking()
			.OrderBy(x => x.Id)
			.Skip(startIndex)
			.Take(count)
			.ToListAsync(cancellationToken);
		var sourceUserDictionary = sourceUserList.ToDictionary(x => x.Id);

		var userMapList = sourceUserList
			.Select(x => UserMap.Create(Guid.NewGuid(), x.Id, new UserId(Guid.NewGuid())))
			.ToList();

		var userList = userMapList
			.Select(x =>
			{
				var password = Password.Create(sourceUserDictionary[x.From].Password);
				var username = UserName.Create(sourceUserDictionary[x.From].Username);
				var roleId = new RoleId(Constants.WatcherRoleId);
				return User.Create(
					x.To,
					username,
					passwordService.Hash(password),
					roleId);
			})
				.ToList();

		var organizationId = new OrganizationId(Constants.OrganizationId);
		var members = userList
			.Select(x => Member.Create(organizationId, x.Id))
			.ToList();

		using var middlewareTransaction = await contexts.Middleware.Database
			.BeginTransactionAsync(cancellationToken);

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

			await contexts.Middleware.Users.AddRangeAsync(userMapList, cancellationToken);
			_ = await contexts.Middleware.SaveChangesAsync(cancellationToken);

			logger.LogInformation("User maps are saved");

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

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

				await contexts.Destination.Users.AddRangeAsync(userList, cancellationToken);
				await contexts.Destination.Members.AddRangeAsync(members, cancellationToken);
				_ = await contexts.Destination.SaveChangesAsync(cancellationToken);

				logger.LogInformation("User are saved");

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

				throw;
			}

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

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