using GeoRitmMover.Mover.Data;

using Microsoft.EntityFrameworkCore;

using Monitoring.Core.Domain;
using Monitoring.Core.Domain.AccessPrimitives;
using Monitoring.Core.Domain.AssetGroupManagement;
using Monitoring.Core.Domain.MemberPrimitives;
using Monitoring.Core.Domain.OrganizationManagement;

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

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

		var accessMapList = await contexts.Middleware.Accesses
			.ToListAsync(cancellationToken);
		var accessIds = accessMapList.Select(u => u.To).ToList();

		var organizationId = new OrganizationId(Constants.OrganizationId);

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

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

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

		var sourceAuthMapList = await contexts.Source.AuthMaps
			.AsNoTracking()
			.OrderBy(x => x.UserId)
			.ThenBy(x => x.InventoryId)
			.Skip(startIndex)
			.Take(count)
			.ToListAsync(cancellationToken);

		var accessMapList = sourceAuthMapList
			.Select(x => AccessMap.Create(Guid.NewGuid(),
				new(x.UserId, x.InventoryId), new AccessId(Guid.NewGuid())))
			.ToList();

		var userMapList = await contexts.Middleware.Users
			.ToListAsync(cancellationToken);

		var members = await contexts.Destination.Members
			.ToListAsync(cancellationToken);

		var assetGroupMaps = await contexts.Middleware.Groups
			.ToListAsync(cancellationToken);

		accessMapList = accessMapList
			.Where(x => assetGroupMaps.Any(y => y.From == x.From.InventoryId))
			.ToList();

		var accessList = accessMapList
			.Select(x =>
			{
				var memberId = GetMemberId(x, userMapList, members);
				var assetGroupId = GetAssetGroupId(x, assetGroupMaps);

				return Access.Create(
					x.To,
					assetGroupId,
					memberId);
			})
				.ToList();

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

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

			await contexts.Middleware.Accesses.AddRangeAsync(accessMapList, cancellationToken);
			_ = await contexts.Middleware.SaveChangesAsync(cancellationToken);

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

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

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

				await contexts.Destination.Accesses.AddRangeAsync(accessList, cancellationToken);
				_ = await contexts.Destination.SaveChangesAsync(cancellationToken);

				logger.LogInformation("Accesses 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);
	}

	private static AssetGroupId GetAssetGroupId(
		AccessMap accessMap,
		List<AssetGroupMap> assetGroupMaps)
	{
		var sourceAssetGroupId = accessMap.From.InventoryId;
		var assetGroupMap = assetGroupMaps.FirstOrDefault(x => x.From == sourceAssetGroupId)
			?? throw new InvalidOperationException("Asset group not found");

		return assetGroupMap.To;
	}

	private static MemberId GetMemberId(AccessMap accessMap, List<UserMap> userMaps, List<Member> members)
	{
		var sourceUserId = accessMap.From.UserId;
		var userMap = userMaps.FirstOrDefault(x => x.From == sourceUserId)
			?? throw new InvalidOperationException("User not found");
		var member = members.FirstOrDefault(x => x.UserId == userMap.To)
			?? throw new InvalidOperationException("Member not found");

		return member.Id;
	}
}
