using CSharpFunctionalExtensions;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Monitoring.Common;
using Monitoring.Controllers;
using Monitoring.Core.Domain.AssetGroupManagement;
using Monitoring.Core.Domain.AssetManagement;
using Monitoring.Core.Domain.OrganizationManagement;
using Monitoring.Extensions;
using Monitoring.Features.AssetGroups.GetAssetGroupList;
using Monitoring.Infrastructure.Data;
using Monitoring.Models;
using Monitoring.Services;

namespace Monitoring.Monolith.Features.AssetGroups;

[Route("/api/asset-groups")]
[ApiController]
[Authorize(Roles = $"{Roles.Manager},{Roles.Watcher}")]
public partial class AssetsGroupsController(
	DataContext dataContext,
	INotificationService notificationService,
	UsersService usersService
) : BaseController
{
	[HttpGet(Name = "GetAssetGroupList")]
	[ProducesResponseType<TableDataResponseModel<AssetGroupModel>>(StatusCodes.Status200OK)]
	public async Task<IActionResult> GetTable(
		[FromQuery] GetAssetGroupListRequest request,
		CancellationToken cancellationToken
	)
	{
		IQueryable<AssetGroup> query = dataContext.AssetGroups;

		if (request.OrganizationId.HasValue)
		{
			OrganizationId organizationId = new(request.OrganizationId.Value);
			query = query.Where(x => x.OrganizationId == organizationId);
		}

		if (Role == Roles.Watcher)
		{
			query = query.Where(x => x.Accesses.Any(x => x.Member.UserId == UserId));
		}

		return Ok(
			await query
				.Where(x => x.Organization.Members.Any(member => member.UserId == UserId))
				.GetTable(request, AssetGroupModel.Projection, cancellationToken)
		);
	}

	[HttpGet("{id:guid}", Name = "GetAssetGroup")]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<AssetGroupModel>(StatusCodes.Status200OK)]
	public async Task<IActionResult> Get(Guid id, CancellationToken cancellationToken)
	{
		AssetGroupId assetGroupId = new(id);
		var assetGroup = await dataContext
			.AssetGroups.Include(x => x.Assets!)
			.Where(x =>
				x.Id == assetGroupId
				&& x.Organization!.Members!.Any(member => member.UserId == UserId)
			)
			.Select(AssetGroupModel.Projection)
			.FirstOrDefaultAsync(cancellationToken);
		return assetGroup is null ? NotFound(nameof(AssetGroup)) : Ok(assetGroup);
	}

	[HttpPost(Name = "CreateAssetGroup")]
	[Authorize(Roles = Roles.Manager)]
	[ProducesResponseType(StatusCodes.Status409Conflict)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<AssetGroupModel>(StatusCodes.Status200OK)]
	public async Task<IActionResult> Create(
		CreateRequest request,
		CancellationToken cancellationToken
	)
	{
		var assetGroupName = AssetGroupName.Create(request.Name);
		var assetGroup = await dataContext.AssetGroups.FirstOrDefaultAsync(
			x =>
				x.Name == assetGroupName
				&& x.Organization!.Members!.Any(member => member.UserId == UserId),
			cancellationToken
		);
		if (assetGroup is not null)
		{
			return Conflict();
		}

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

		assetGroup = AssetGroup.Create(assetGroupName, organization.Id, request.HasPBX);

		_ = await dataContext.AssetGroups.AddAsync(assetGroup, cancellationToken);
		_ = await dataContext.SaveChangesAsync(cancellationToken);

		var model = await dataContext
			.AssetGroups.Where(x => x.Id == assetGroup.Id)
			.Select(AssetGroupModel.Projection)
			.FirstAsync(cancellationToken);
		await NotifyUsers(
			model,
			assetGroup.OrganizationId,
			"ASSET-GROUPS.CREATED",
			cancellationToken
		);

		return Ok(model);
	}

	private async Task NotifyUsers(
		AssetGroupModel assetGroupModel,
		OrganizationId organizationId,
		string method,
		CancellationToken cancellationToken
	)
	{
		await notificationService.NotifyUsers(
			await usersService.GetUserIdListByOrganization(organizationId, cancellationToken),
			method,
			assetGroupModel,
			cancellationToken
		);
	}

	[HttpPut("{id:guid}", Name = "UpdateAssetGroup")]
	[Authorize(Roles = Roles.Manager)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	public async Task<IActionResult> Update(
		Guid id,
		UpdateRequest request,
		CancellationToken cancellationToken
	)
	{
		AssetGroupId assetGroupId = new(id);
		var assetGroup = await dataContext
			.AssetGroups.Include(x => x.Assets!)
			.FirstOrDefaultAsync(
				x =>
					x.Id == assetGroupId
					&& x.Organization!.Members!.Any(member => member.UserId == UserId),
				cancellationToken
			);
		if (assetGroup is null)
		{
			return NotFound(nameof(Asset));
		}

		var assetGroupName = AssetGroupName.Create(request.Name);
		assetGroup.Update(assetGroupName, request.HasPBX);

		_ = await dataContext.SaveChangesAsync(cancellationToken);

		var model = await dataContext
			.AssetGroups.Where(x => x.Id == assetGroup.Id)
			.Select(AssetGroupModel.Projection)
			.FirstAsync(cancellationToken);
		await NotifyUsers(
			model,
			assetGroup.OrganizationId,
			"ASSET-GROUPS.UPDATED",
			cancellationToken
		);

		return NoContent();
	}

	[HttpDelete("{id:guid}", Name = "DeleteAssetGroup")]
	[Authorize(Roles = Roles.Manager)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	public async Task<IActionResult> Delete(Guid id, CancellationToken cancellationToken)
	{
		AssetGroupId assetGroupId = new(id);
		var assetGroup = await dataContext
			.AssetGroups.Include(x => x.Assets!)
			.FirstOrDefaultAsync(
				x =>
					x.Id == assetGroupId
					&& x.Organization!.Members!.Any(member => member.UserId == UserId),
				cancellationToken
			);
		if (assetGroup is null)
		{
			return NotFound(nameof(AssetGroup));
		}

		var model = await dataContext
			.AssetGroups.Where(x => x.Id == assetGroup.Id)
			.Select(AssetGroupModel.Projection)
			.FirstAsync(cancellationToken);

		_ = dataContext.AssetGroups.Remove(assetGroup);
		_ = await dataContext.SaveChangesAsync(cancellationToken);

		await NotifyUsers(
			model,
			assetGroup.OrganizationId,
			"ASSET-GROUPS.DELETED",
			cancellationToken
		);

		return NoContent();
	}
}
