using System.Linq.Expressions;

using FluentValidation;

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.MemberPrimitives;
using Monitoring.Core.Domain.OrganizationManagement;
using Monitoring.Core.Domain.UserPrimitives;
using Monitoring.Extensions;
using Monitoring.Infrastructure.Data;
using Monitoring.Interfaces;
using Monitoring.Models;
using Monitoring.Services;

namespace Monitoring.Features.Members;

[Route("/api/members")]
[Authorize(Roles = $"{Roles.Administrator}, {Roles.Manager}")]
public class MemberController(
	DataContext dataContext,
	INotificationService notificationService
) : BaseController
{
	public record MemberModel(Guid Id, Guid UserId, string Username, Guid OrganizationId)
	{
		internal static Expression<Func<Member, MemberModel>> FromEntity = (x) =>
			new MemberModel(x.Id.Value, x.UserId.Value, x.User.Username.Value, x.OrganizationId.Value);
	}

	public record GetListRequest(int? Skip, int? Take, Guid OrganizationId) : ITableState;
	public record CreateRequest(Guid OrganizationId, Guid UserId);

	public class CreateRequestValidator : AbstractValidator<CreateRequest>
	{
		public CreateRequestValidator()
		{
			RuleFor(x => x.UserId).NotEmpty().WithMessage("User id is required.");
			RuleFor(x => x.OrganizationId).NotEmpty().WithMessage("Organization id is required.");
		}
	}

	[HttpGet(Name = "GetMemberList")]
	[ProducesResponseType<TableDataResponseModel<MemberModel>>(StatusCodes.Status200OK)]
	public async Task<IActionResult> GetList(
		[FromQuery] GetListRequest request, CancellationToken cancellationToken)
	{
		var organizationId = new OrganizationId(request.OrganizationId);
		var query = dataContext.Members
			.Where(x => x.OrganizationId == organizationId);

		if (Role == Roles.Manager)
		{
			query = query.Where(x => x.Organization!.Members.Any(x => x.UserId == UserId));
		}

		return Ok(await dataContext.Members
			.Where(x => x.OrganizationId == organizationId)
			.Include(x => x.User)
			.Select(MemberModel.FromEntity)
			.GetTable(request, cancellationToken));
	}

	[HttpGet("{id:guid}", Name = "GetMember")]
	[ProducesResponseType<MemberModel>(StatusCodes.Status200OK)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	public async Task<IActionResult> Get(Guid id, CancellationToken cancellationToken)
	{
		IQueryable<Member> query = dataContext.Members;

		if (Role == Roles.Manager)
		{
			query = query.Where(x => x.Organization!.Members.Any(x => x.UserId == UserId));
		}

		var memberId = new MemberId(id);
		var member = await dataContext.Members
			.Include(x => x.User)
			.Where(x => x.Id == memberId)
			.Select(MemberModel.FromEntity)
			.FirstOrDefaultAsync(cancellationToken);
		return member is null ? NotFound(nameof(Member)) : Ok(member);
	}

	[HttpPost(Name = "CreateMember")]
	[Authorize(Roles = Roles.Administrator)]
	[ProducesResponseType<MemberModel>(StatusCodes.Status200OK)]
	[ProducesResponseType(StatusCodes.Status409Conflict)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	public async Task<IActionResult> Create(CreateRequest request, CancellationToken cancellationToken)
	{
		var userId = new UserId(request.UserId);
		var organizationId = new OrganizationId(request.OrganizationId);
		var existingMember = await dataContext.Members
			.FirstOrDefaultAsync(x => x.UserId == userId && x.OrganizationId == organizationId, cancellationToken);
		if (existingMember is not null)
		{
			return Conflict();
		}

		var organization = await dataContext.Organizations
			.FirstOrDefaultAsync(x => x.Id == organizationId, cancellationToken);
		if (organization is null)
		{
			return NotFound(nameof(Organization));
		}

		var user = await dataContext.Users
			.FirstOrDefaultAsync(x => x.Id == userId, cancellationToken);
		if (user is null)
		{
			return NotFound(nameof(User));
		}

		var member = Member.Create(organization.Id, user.Id);

		_ = await dataContext.Members.AddAsync(member, cancellationToken);
		_ = await dataContext.SaveChangesAsync(cancellationToken);

		var model = await dataContext.Members
			.Where(x => x.Id == member.Id)
			.Select(MemberModel.FromEntity)
			.FirstAsync(cancellationToken);

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

		return Ok(model);
	}

	[HttpDelete("{id:guid}", Name = "DeleteMember")]
	[Authorize(Roles = Roles.Administrator)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	public async Task<IActionResult> Delete(Guid id, CancellationToken cancellationToken)
	{
		var memberId = new MemberId(id);
		var member = await dataContext.Members
			.Include(x => x.User!)
			.Include(x => x.Organization!)
			.Where(x => x.Id == memberId)
			.Select(MemberModel.FromEntity)
			.FirstOrDefaultAsync(cancellationToken);
		if (member is null)
		{
			return NotFound(nameof(Member));
		}

		_ = dataContext.Members
			.Where(x => x.Id == memberId)
			.ExecuteDeleteAsync(cancellationToken);

		_ = await dataContext.SaveChangesAsync(cancellationToken);

		await notificationService.NotifyAdministrators(member,
			"MEMBERS.DELETED", cancellationToken);

		return NoContent();
	}
}
