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.AssetManagement;
using Monitoring.Core.Domain.Primitives;
using Monitoring.Extensions;
using Monitoring.Features.Areas.CreateArea;
using Monitoring.Features.Areas.GetAreaList;
using Monitoring.Features.Areas.UpdateArea;
using Monitoring.Infrastructure.Data;
using Monitoring.Models;
using Monitoring.Services;

namespace Monitoring.Features.Areas;
[Route("api/areas")]
[Authorize(Roles = $"{Roles.Manager},{Roles.Watcher}")]
public class AreasController(
	DataContext dataContext,
	INotificationService notificationService,
	UsersService usersService
) : BaseController
{
	[HttpGet(Name = "GetAreaList")]
	[ProducesResponseType<TableDataResponseModel<AreaModel>>(StatusCodes.Status200OK)]
	public async Task<IActionResult> GetList(
		[FromQuery] GetAreaListRequest request,
		CancellationToken cancellationToken)
	{
		AssetId assetId = new(request.AssetId);
		return Ok(
			await dataContext.Areas
				.Where(x => x.AssetId == assetId &&
					x.Asset.Organization.Members.Any(y => y.UserId == UserId))
				.ApplySort(request)
				.GetTable(request, AreaModel.Projection, cancellationToken)
		);
	}

	[HttpGet("{id:guid}", Name = "GetArea")]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<AreaModel>(StatusCodes.Status200OK)]
	public async Task<IActionResult> Get(Guid id, CancellationToken cancellationToken)
	{
		var areaId = new AreaId(id);
		var area = await dataContext.Areas
			.Where(x => x.Id == areaId &&
				x.Asset.Organization.Members.Any(y => y.UserId == UserId))
			.AsNoTracking()
			.Select(AreaModel.Projection)
			.FirstOrDefaultAsync(cancellationToken);

		return area is null
			? NotFound(nameof(Area))
			: Ok(area);
	}

	[HttpPost(Name = "CreateArea")]
	[Authorize(Roles = Roles.Manager)]
	[ProducesResponseType(StatusCodes.Status409Conflict)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<AreaModel>(StatusCodes.Status200OK)]
	public async Task<IActionResult> Post(CreateAreaRequest request, CancellationToken cancellationToken)
	{
		AssetId assetId = new(request.AssetId);
		var asset = await dataContext.Assets
			.AsNoTracking()
			.FirstOrDefaultAsync(x => x.Id == assetId
				&& x.Organization.Members.Any(x => x.UserId == UserId), cancellationToken);
		if (asset is null)
		{
			return NotFound(nameof(Asset));
		}

		AreaIndex areaIndex = new(request.Index);
		var area = await dataContext.Areas
			.FirstOrDefaultAsync(x => x.AssetId == assetId && x.Index == areaIndex, cancellationToken);
		if (area is not null)
		{
			return Conflict();
		}

		var name = AreaName.Create(request.Name);
		var description = AreaDescription.Create(request.Description);
		area = Area.Create(new(request.Index), name, description, assetId);

		_ = await dataContext.Areas.AddAsync(area, cancellationToken);
		_ = await dataContext.SaveChangesAsync(cancellationToken);

		var model = await dataContext.Areas
			.Where(x => x.Id == area.Id &&
				x.Asset!.Organization!.Members!.Any(x => x.UserId == UserId))
			.Select(AreaModel.Projection)
			.FirstAsync(cancellationToken);

		await notificationService
			.NotifyUsers(await usersService.GetUserIdListByOrganization(asset.OrganizationId, cancellationToken),
				"AREAS.CREATED", model, cancellationToken);

		return Ok(model);
	}

	[HttpPut("{id:guid}", Name = "UpdateArea")]
	[Authorize(Roles = Roles.Manager)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<string>(StatusCodes.Status409Conflict)]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	public async Task<IActionResult> Put([FromRoute] Guid id, UpdateAreaRequest request, CancellationToken cancellationToken)
	{
		var areaId = new AreaId(id);
		var area = await dataContext.Areas
			.Include(x => x.Asset!)
			.FirstOrDefaultAsync(x => x.Id == areaId &&
				x.Asset.Organization.Members.Any(m => m.UserId == UserId), cancellationToken);
		if (area is null)
		{
			return NotFound(nameof(Area));
		}

		var areaIndex = new AreaIndex(request.Index);
		var sameArea = await dataContext.Areas
			.FirstOrDefaultAsync(x => x.Id != areaId &&
				x.Index == areaIndex &&
				x.AssetId == area.AssetId, cancellationToken);
		if (sameArea is not null)
		{
			return Conflict("The same area with the same index is already in the system");
		}

		AreaIndex index = new(request.Index);
		var name = AreaName.Create(request.Name);
		var description = AreaDescription.Create(request.Description);
		area.Update(index, name, description);

		_ = await dataContext.SaveChangesAsync(cancellationToken);

		var model = await dataContext.Areas
			.Where(x => x.Id == area.Id &&
				x.Asset!.Organization!.Members!.Any(x => x.UserId == UserId))
			.Select(AreaModel.Projection)
			.FirstAsync(cancellationToken);

		await notificationService
			.NotifyUsers(await usersService.GetUserIdListByOrganization(area.Asset!.OrganizationId, cancellationToken),
				"AREAS.UPDATED", model, cancellationToken);

		return NoContent();
	}

	[HttpDelete("{id:guid}", Name = "DeleteArea")]
	[Authorize(Roles = Roles.Manager)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	public async Task<IActionResult> Delete([FromRoute] Guid id, CancellationToken cancellationToken)
	{
		var area = await dataContext.Areas
			.Include(x => x.Asset!)
			.FirstOrDefaultAsync(x => x.Id.Value == id &&
				x.Asset.Organization.Members.Any(m => m.UserId == UserId), cancellationToken);
		if (area is null)
		{
			return NotFound(nameof(Area));
		}

		var model = await dataContext.Areas
			.Where(x => x.Id == area.Id &&
				x.Asset!.Organization!.Members!.Any(x => x.UserId == UserId))
			.Select(AreaModel.Projection)
			.FirstAsync(cancellationToken);

		_ = dataContext.Areas.Remove(area);
		_ = await dataContext.SaveChangesAsync(cancellationToken);

		await notificationService
			.NotifyUsers(await usersService.GetUserIdListByOrganization(area.Asset!.OrganizationId, cancellationToken),
				"AREAS.DELETED", model, cancellationToken);

		return NoContent();
	}
}
