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

using CSharpFunctionalExtensions;

using Microsoft.EntityFrameworkCore;

using Monitoring.Core.Domain.AssetManagement;
using Monitoring.Core.Domain.ReportManagement;
using Monitoring.Core.Domain.UserPrimitives;
using Monitoring.Extensions;
using Monitoring.Features.Reports.Contracts;
using Monitoring.Features.Reports.ReportFactories;
using Monitoring.Infrastructure.Data;
using Monitoring.Models;

using Serilog;

namespace Monitoring.Features.Reports;

public partial class ReportRepository(
	DataContext dataContext,
	ILogger<ReportRepository> logger
)
{
	private readonly Dictionary<ReportType, IReportFactory> _factories = new()
	{
		[ReportType.ByState] = new ByStateReportFactory(),
		[ReportType.Journal] = new JournalReportFactory(),
		[ReportType.SummaryEvents] = new SummaryEventsReportFactory(),
	};

	public async ValueTask<Result<DataTable>> Export(
		CreateExportRequest request,
		UserId userId,
		CancellationToken cancellationToken
	)
	{
		try
		{
			var reportId = new ReportId(request.ReportId);
			var report = await dataContext.Reports
				.FirstOrDefaultAsync(x => x.Id == reportId && x.UserId == userId, cancellationToken);
			if (report is null)
			{
				return Result.Failure<DataTable>("Report not found");
			}

			var context = new MakeExportContext(report, request.Language, request.TimeZone);			
			return _factories[report.Type].Export(context);
		}
		catch (Exception ex)
		{
			return Result.Failure<DataTable>(ex.Message);
		}
	}

	internal async Task<Result<GetReportResponse>> Get(
		Guid id,
		UserId userId,
		CancellationToken cancellationToken)
	{
		var reportId = new ReportId(id);
		var report = await dataContext.Reports
			.FirstOrDefaultAsync(x => x.Id == reportId && x.UserId == userId, cancellationToken);
		if (report is null)
		{
			return Result.Failure<GetReportResponse>("NotFound");
		}

		var response = new GetReportResponse(
			id,
			userId.Value,
			report.Name.Value,
			report.CreatedAt,
			report.Data,
			report.Type
		);

		return Result.Success(response);
	}

	internal async Task<Result<TableDataResponseModel<GetReportsResponseItem>>> GetList(
		UserId userId,
		GetReportListRequest request,
		CancellationToken cancellationToken)
	{
		try
		{
			var query = dataContext.Reports
				.Where(x => x.UserId == userId);

			if (!string.IsNullOrEmpty(request.Search))
			{
				query = query.Where(x => x.Name.Value.Contains(request.Search, StringComparison.OrdinalIgnoreCase));
			}

			if (!string.IsNullOrEmpty(request.ByDate))
			{
				if (request.ByDate == SortDirection.Ascending)
				{
					query = query.OrderBy(x => x.CreatedAt);
				}
				else if (request.ByDate == SortDirection.Descending)
				{
					query = query.OrderByDescending(x => x.CreatedAt);
				}
			}

			if (!string.IsNullOrEmpty(request.Alphabetic))
			{
				if (request.Alphabetic == SortDirection.Ascending)
				{
					query = query.OrderBy(x => x.Type);
				}
				else if (request.Alphabetic == SortDirection.Descending)
				{
					query = query.OrderByDescending(x => x.Type);
				}
			}

			return await query
				.GetTable(request, x => new GetReportsResponseItem(
					x.Id.Value,
					x.UserId.Value,
					x.Name.Value,
					x.CreatedAt,
					x.Type
				), cancellationToken);
		}
		catch (Exception ex)
		{
			return Result.Failure<TableDataResponseModel<GetReportsResponseItem>>(ex.Message);
		}
	}

	public async Task<Result> Delete(Guid id, UserId userId, CancellationToken cancellationToken)
	{
		var reportId = new ReportId(id);
		var report = await dataContext.Reports.FirstOrDefaultAsync(x => x.Id == reportId && x.UserId == userId, cancellationToken);
		if (report is null)
		{
			return Result.Failure("Report not found");
		}

		dataContext.Reports.Remove(report);
		await dataContext.SaveChangesAsync(cancellationToken);
		return Result.Success();
	}

	public async Task<Result> DeleteAll(UserId userId, CancellationToken cancellationToken)
	{
		var reports = await dataContext.Reports.Where(x => x.UserId == userId).ToListAsync(cancellationToken);
		dataContext.Reports.RemoveRange(reports);
		await dataContext.SaveChangesAsync(cancellationToken);
		return Result.Success();
	}

	internal async Task<Result<CreateReportResponse>> Create(
		CreateReportRequest request,
		UserId userId,
		CancellationToken cancellationToken)
	{
		try
		{
			var assetIds = request.AssetIdList.Select(x => new AssetId(x)).ToList();

			var context = new MakeReportContext(dataContext, userId, assetIds, request.Extra, request.TimeZone);

			var data = await _factories[request.Type].Report(context, cancellationToken);
			if (data.IsFailure)
			{
				return Result.Failure<CreateReportResponse>(data.Error);
			}

			var report = new Report(
				ReportName.Create("Not implemented feature"),
				data.Value,
				userId,
				request.Type
			);
			await dataContext.Reports.AddAsync(report, cancellationToken);
			await dataContext.SaveChangesAsync(cancellationToken);

			return Result.Success(CreateReportResponse.FromEntity(report));
		}
		catch (JsonException ex)
		{
			Log.Information("Json exception {Message}", ex.Message);
			return Result.Failure<CreateReportResponse>("Extra property is invalid");
		}
	}
}
