using System.Data;
using System.Text.Json;

using CSharpFunctionalExtensions;

using Microsoft.EntityFrameworkCore;

namespace Monitoring.Features.Reports.ReportFactories;

internal class ByStateReportFactory : IReportFactory
{
	private record Item(
		List<string> Values
	);

	private record Data(
		IEnumerable<Item> Items,
		IEnumerable<string> Properties
	);

	private record Extra(
		IEnumerable<string> Properties,
		DateTime? From,
		DateTime? To
	);

	private record AssetPrepared(
		string Id,
		string Name,
		string IsOnline,
		string Description,
		string Address,
		string Groups
	);

	private static readonly Dictionary<string, Func<AssetPrepared, string>> PropertyGetter = new()
	{
		{ "Id", x => x.Id },
		{ "Name", x => x.Name },
		{ "IsOnline", x => x.IsOnline },
		{ "Description", x => x.Description },
		{ "Address", x => x.Address },
		{ "Groups", x => x.Groups },
	};

	public Result<DataTable> Export(MakeExportContext context)
	{
		var dataResult = JsonSerializer.Deserialize<Data>(context.Report.Data);
		if (dataResult is null)
		{
			return Result.Failure<DataTable>("Report data is failed to deserialize to Data");
		}

		DataTable dataTable = new("Assets");

		var indexOfIsOnlineColumn = -1;
		foreach (var property in dataResult.Properties)
		{
			dataTable.Columns.Add(Translations.Translate(context.Language, property), typeof(CellContainer));
			if (property == "IsOnline")
			{
				indexOfIsOnlineColumn = dataResult.Properties.ToList().IndexOf(property);
			}
		}

		foreach (var log in dataResult.Items)
		{
			var objects = new List<CellContainer>();

			for (var i = 0; i < dataResult.Properties.Count(); i++)
			{
				if (indexOfIsOnlineColumn == i)
				{
					objects.Add(new CellContainer(Translations.Translate(context.Language, log.Values.ElementAt(i)), 1));
				}
				else
				{
					objects.Add(new CellContainer(log.Values.ElementAt(i), 1));
				}
			}

			_ = dataTable.Rows.Add([.. objects]);
		}

		return dataTable;
	}

	public async ValueTask<Result<string>> Report(MakeReportContext context, CancellationToken cancellationToken)
	{
		if (string.IsNullOrEmpty(context.Extra))
		{
			return Result.Failure<string>("Extra is required.");
		}
		var extra = JsonSerializer.Deserialize<Extra>(context.Extra);
		if (extra is null || extra.Properties is null || !extra.Properties.Any())
		{
			return Result.Failure<string>("Extra format error.");
		}

		var query = context.DataContext.Assets
			.AsNoTracking()
			.Where(x => x.Organization.Members.Any(x => x.UserId == context.UserId))
			.Where(x => context.AssetIdList.Contains(x.Id));

		if (extra.From.HasValue && extra.To.HasValue)
		{
			query = query.Where(x => x.Events.Any(e => e.CreatedAt >= extra.From.Value && e.CreatedAt <= extra.To.Value));
		}

		var assets = await query
			.Select(x => new AssetPrepared(
				x.Id.Value.ToString(),
				x.Name.Value,
				x.IsOnline ? "Online" : "Offline",
				x.Description.Value,
				x.Address.ToString(),
				string.Join(", ", x.Groups.OrderBy(x => x.Name).Select(x => x.Name.Value))
			))
			.ToListAsync(cancellationToken);

		var items = new List<Item>();
		foreach (var asset in assets)
		{
			var properties = new List<string>();
			foreach (var property in extra.Properties)
			{
				properties.Add(PropertyGetter[property](asset));
			}

			items.Add(new Item(properties));
		}
		var data = new Data(items, extra.Properties);

		return Result.Success(JsonSerializer.Serialize(data));
	}
}
