using System.Linq.Dynamic.Core;

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.Zones.CreateZone;
using Monitoring.Features.Zones.GetZoneList;
using Monitoring.Features.Zones.UpdateZone;
using Monitoring.Infrastructure.Data;
using Monitoring.Models;
using Monitoring.Services;

namespace Monitoring.Features.Zones;
[Route("api/zones")]
[Authorize(Roles = $"{Roles.Manager},{Roles.Watcher}")]
public class ZonesController(
	DataContext dataContext,
	INotificationService notificationService,
	UsersService usersService
) : BaseController
{
	[HttpGet(Name = "GetZoneList")]
	[ProducesResponseType<TableDataResponseModel<ZoneModel>>(StatusCodes.Status200OK)]
	public async Task<IActionResult> GetZones(
		[FromQuery] GetZoneListRequest request,
		CancellationToken cancellationToken)
	{
		var assetId = new AssetId(request.AssetId);
		return Ok(
			await dataContext.Zones
				.AsNoTracking()
				.Where(x =>
					(Role == Roles.Manager && x.Area.Asset.Organization.Members.Any(y => y.UserId == UserId)) ||
					(Role == Roles.Watcher && x.Area.Asset.Groups.Any(y => y.Accesses.Any(z => z.Member.UserId == UserId))))
				.Where(x => x.Area.AssetId == assetId)
				.ApplySort(request)
				.GetTable(request, ZoneModel.Projection, cancellationToken)
		);
	}

	[HttpGet("{id:guid}", Name = "GetZone")]
	[ProducesResponseType<ZoneModel>(StatusCodes.Status200OK)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	public async Task<IActionResult> Get([FromRoute] Guid id, CancellationToken cancellationToken)
	{
		var zoneId = new ZoneId(id);
		var zone = await dataContext.Zones
			.Where(x => x.Id == zoneId && (
				(Role == Roles.Manager && x.Area.Asset.Organization.Members.Any(y => y.UserId == UserId)) ||
				(Role == Roles.Watcher && x.Area.Asset.Groups.Any(y => y.Accesses.Any(z => z.Member.UserId == UserId)))))
			.AsNoTracking()
			.Select(ZoneModel.Projection)
			.FirstOrDefaultAsync(cancellationToken);

		return zone is null
			? NotFound(nameof(Zone))
			: Ok(zone);
	}

	[HttpPost(Name = "CreateZone")]
	[Authorize(Roles = Roles.Manager)]
	[ProducesResponseType<ZoneModel>(StatusCodes.Status200OK)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<string>(StatusCodes.Status409Conflict)]
	public async Task<IActionResult> Post(CreateZoneRequest request, CancellationToken cancellationToken)
	{
		var areaId = new AreaId(request.AreaId);
		var hasAccessToArea = await dataContext.Areas
			.AnyAsync(x => x.Id == areaId
				&& x.Asset.Organization.Members.Any(x => x.UserId == UserId),
				cancellationToken);
		if (!hasAccessToArea)
		{
			return NotFound(nameof(Area));
		}

		var zoneIndex = new ZoneIndex(request.Index);
		var existingZone = await dataContext.Zones
			.FirstOrDefaultAsync(x => x.AreaId == areaId
				&& x.Index == zoneIndex,
				cancellationToken);
		if (existingZone is not null)
		{
			return Conflict("The same zone with the same index is already in the system");
		}

		ZoneIndex index = new(request.Index);
		var name = ZoneName.Create(request.Name);
		var description = ZoneDescription.Create(request.Description);

		existingZone = Zone.Create(
			index,
			name,
			description,
			areaId);

		_ = await dataContext.Zones.AddAsync(existingZone, cancellationToken);
		_ = await dataContext.SaveChangesAsync(cancellationToken);

		var asset = await dataContext.Assets
			.FirstAsync(x => x.Areas!.Any(x => x.Id == areaId), cancellationToken);

		var model = await dataContext.Zones
			.Where(x => x.Id == existingZone.Id &&
				x.Area.Asset.Organization.Members.Any(x => x.UserId == UserId))
			.Select(ZoneModel.Projection)
			.FirstAsync(cancellationToken);

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

		return Ok(model);
	}

	[HttpPut("{id:guid}", Name = "UpdateZone")]
	[Authorize(Roles = Roles.Manager)]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<string>(StatusCodes.Status409Conflict)]
	public async Task<IActionResult> Put([FromRoute] Guid id, UpdateZoneRequest request, CancellationToken cancellationToken)
	{
		var zoneId = new ZoneId(id);
		var zoneIndex = new ZoneIndex(request.Index);
		var zone = await dataContext.Zones
			.Include(x => x.Area.Asset)
			.FirstOrDefaultAsync(x => x.Id == zoneId &&
				x.Area.Asset.Organization.Members.Any(m => m.UserId == UserId), cancellationToken);
		if (zone is null)
		{
			return NotFound(nameof(Zone));
		}

		var sameZone = await dataContext.Zones
			.FirstOrDefaultAsync(x => x.Id != zoneId &&
				x.Index == zoneIndex &&
				x.AreaId == zone.AreaId, cancellationToken);
		if (sameZone is not null)
		{
			return Conflict("The same zone with the same index is already in the system");
		}

		ZoneIndex index = new(request.Index);
		var name = ZoneName.Create(request.Name);
		var description = ZoneDescription.Create(request.Description);
		zone.Update(index, name, description);
		_ = await dataContext.SaveChangesAsync(cancellationToken);

		var model = await dataContext.Zones
			.Where(x => x.Id == zoneId &&
				x.Area.Asset.Organization.Members.Any(x => x.UserId == UserId))
			.Select(ZoneModel.Projection)
			.FirstAsync(cancellationToken);

		await notificationService
			.NotifyUsers(await usersService.GetUserIdListByOrganization(zone.Area.Asset.OrganizationId, cancellationToken),
				"ZONES.UPDATED", model, cancellationToken);

		return NoContent();
	}

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

		var model = await dataContext.Zones
			.Where(x => x.Id == zoneId &&
				x.Area.Asset.Organization.Members.Any(x => x.UserId == UserId))
			.Select(ZoneModel.Projection)
			.FirstAsync(cancellationToken);

		_ = dataContext.Zones.Remove(zone);
		_ = await dataContext.SaveChangesAsync(cancellationToken);

		await notificationService
			.NotifyUsers(await usersService.GetUserIdListByOrganization(zone.Area.Asset.OrganizationId, cancellationToken),
				"ZONES.DELETED", model, cancellationToken);

		return NoContent();
	}
}
