using System.Linq.Expressions;

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

using Monitoring.Common;
using Monitoring.Controllers;
using Monitoring.Core.Domain;
using Monitoring.Core.Domain.AccessPrimitives;
using Monitoring.Core.Domain.AssetGroupManagement;
using Monitoring.Core.Domain.MemberPrimitives;
using Monitoring.Extensions;
using Monitoring.Infrastructure.Data;
using Monitoring.Interfaces;
using Monitoring.Models;
using Monitoring.Services;

namespace Monitoring.Features.AccessManagement;

[Route("/api/accesses")]
[Authorize(Roles = Roles.Manager)]
public class AccessesController(
	DataContext dataContext,
	INotificationService notificationService
) : BaseController
{
	public record AccessModel(
		Guid Id,
		Guid MemberId,
		string Username,
		Guid AssetGroupId,
		string AssetGroupName)
	{
		internal static Expression<Func<Access, AccessModel>> FromEntity = x => new(
			x.Id.Value,
			x.MemberId.Value,
			x.Member.User.Username.Value,
			x.AssetGroupId.Value,
			x.AssetGroup.Name.Value);
	}

	public record GetListRequest(int? Skip, int? Take, Guid MemberId) : ITableState;
	public record CreateRequest(Guid MemberId, Guid AssetGroupId);

	[HttpGet(Name = "GetAccessList")]
	[ProducesResponseType<TableDataResponseModel<AccessModel>>(StatusCodes.Status200OK)]
	public async Task<IActionResult> GetList(
		[FromQuery] GetListRequest request, CancellationToken cancellationToken)
	{
		MemberId memberId = new(request.MemberId);
		var query = dataContext.Accesses
			.Include(x => x.Member.User)
			.Where(x => x.AssetGroup.Organization.Members.Any(x => x.UserId == UserId))
			.Where(x => x.MemberId == memberId);

		return Ok(await query
			.Select(AccessModel.FromEntity)
			.GetTable(request, cancellationToken));
	}

	[HttpGet("{id:guid}", Name = "GetAccess")]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<AccessModel>(StatusCodes.Status200OK)]
	public async Task<IActionResult> Get(Guid id, CancellationToken cancellationToken)
	{
		AccessId accessId = new(id);
		var access = await dataContext.Accesses
			.Where(x => x.AssetGroup.Organization.Members.Any(x => x.UserId == UserId))
			.Where(x => x.Id == accessId)
			.Select(AccessModel.FromEntity)
			.FirstOrDefaultAsync(cancellationToken);
		if (access is null)
		{
			return NotFound(nameof(Access));
		}

		return Ok(access);
	}

	[HttpPost(Name = "CreateAccess")]
	[ProducesResponseType(StatusCodes.Status409Conflict)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<AccessModel>(StatusCodes.Status200OK)]
	public async Task<IActionResult> Create(CreateRequest request, CancellationToken cancellationToken)
	{
		MemberId memberId = new(request.MemberId);
		AssetGroupId assetGroupId = new(request.AssetGroupId);
		var existingAccess = await dataContext.Accesses
			.Where(x => x.AssetGroup.Organization.Members.Any(x => x.UserId == UserId))
			.FirstOrDefaultAsync(x => x.MemberId == memberId && x.AssetGroupId == assetGroupId, cancellationToken);
		if (existingAccess is not null)
		{
			return Conflict();
		}

		var assetGroup = await dataContext.AssetGroups
			.FirstOrDefaultAsync(x => x.Id == assetGroupId, cancellationToken);
		if (assetGroup is null)
		{
			return NotFound(nameof(AssetGroup));
		}

		var member = await dataContext.Members
			.Include(x => x.User)
			.FirstOrDefaultAsync(x => x.Id == memberId, cancellationToken);
		if (member is null)
		{
			return NotFound(nameof(Member));
		}

		var access = Access.Create(assetGroup.Id, member.Id);

		_ = await dataContext.Accesses.AddAsync(access, cancellationToken);
		_ = await dataContext.SaveChangesAsync(cancellationToken);

		var model = await dataContext.Accesses
			.Where(x => x.AssetGroup.Organization.Members.Any(x => x.UserId == UserId))
			.Where(x => x.Id == access.Id)
			.Select(AccessModel.FromEntity)
			.FirstAsync(cancellationToken);

		await notificationService.NotifyAdministrators(model,
			"ACCESSES.CREATED", cancellationToken);

		return Ok(model);
	}

	[HttpDelete("{id:guid}", Name = "DeleteAccess")]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	public async Task<IActionResult> Delete(Guid id, CancellationToken cancellationToken)
	{
		AccessId accessId = new(id);
		var access = await dataContext.Accesses
			.Include(x => x.Member.User)
			.Include(x => x.AssetGroup)
			.Where(x => x.AssetGroup.Organization.Members.Any(x => x.UserId == UserId))
			.FirstOrDefaultAsync(x => x.Id == accessId, cancellationToken);
		if (access is null)
		{
			return NotFound(nameof(Access));
		}

		var model = await dataContext.Accesses
			.Where(x => x.AssetGroup.Organization.Members.Any(x => x.UserId == UserId))
			.Where(x => x.Id == access.Id)
			.Select(AccessModel.FromEntity)
			.FirstAsync(cancellationToken);

		_ = dataContext.Accesses.Remove(access);

		_ = await dataContext.SaveChangesAsync(cancellationToken);

		await notificationService.NotifyAdministrators(access,
			"ACCESSES.DELETED", cancellationToken);

		return NoContent();
	}
}
