using FluentValidation;

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

using Monitoring.Common;
using Monitoring.Core.Domain.OrganizationManagement;
using Monitoring.Core.Domain.TokenManagement;
using Monitoring.Extensions;
using Monitoring.Infrastructure.Data;
using Monitoring.Interfaces;
using Monitoring.Models;
using Monitoring.Repositories;
using Monitoring.Services;

namespace Monitoring.Features.Tokens;

[Route("/api/tokens")]
[ApiController]
[Authorize(
	AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme,
	Roles = Roles.Manager
)]
public class TokensController(
	DataContext dataContext,
	TokenService tokenService,
	INotificationService notificationService,
	OrganizationRepository organizationRepository
) : ControllerBase
{
	public record TokenModel(Guid Id, string Name, string Code, Guid OrganizationId)
	{
		internal static TokenModel FromEntity(Token x)
		{
			return new TokenModel(x.Id.Value, x.Name.Value, x.Code.Value, x.OrganizationId.Value);
		}
	}
	public record GetListRequest(int? Skip, int? Take, Guid OrganizationId) : ITableState;
	public record CreateRequest(Guid OrganizationId, string Name);
	public class CreateRequestValidator : AbstractValidator<CreateRequest>
	{
		public CreateRequestValidator()
		{
			RuleFor(x => x.OrganizationId)
				.NotEmpty();

			RuleFor(x => x.Name)
				.NotEmpty()
				.MinimumLength(TokenName.MinLength)
				.MaximumLength(TokenName.MaxLength);
		}
	}


	public record UpdateRequest(string Name);

	[HttpGet(Name = "GetTokenList")]
	[ProducesResponseType<TableDataResponseModel<TokenModel>>(StatusCodes.Status200OK)]
	public async Task<IActionResult> GetList(
		[FromQuery] GetListRequest request, CancellationToken cancellationToken)
	{
		var userId = User.GetUserId();
		var organizationId = new OrganizationId(request.OrganizationId);
		return Ok(await dataContext.Tokens
			.Where(x => x.OrganizationId == organizationId
				&& x.Organization!.Members!.Any(x => x.UserId == userId))
			.Select(x => TokenModel.FromEntity(x))
			.GetTable(request, cancellationToken));
	}

	[HttpGet("{id:guid}", Name = "GetToken")]
	[ProducesResponseType<TokenModel>(StatusCodes.Status200OK)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	public async Task<IActionResult> Get(Guid id, CancellationToken cancellationToken)
	{
		var userId = User.GetUserId();
		var tokenId = new TokenId(id);

		var token = await dataContext.Tokens
			.FirstOrDefaultAsync(x => x.Id == tokenId
				&& x.Organization!.Members!.Any(x => x.UserId == userId),
				cancellationToken);
		return token is null ? NotFound(nameof(Token)) : Ok(TokenModel.FromEntity(token));
	}

	[HttpPost(Name = "CreateToken")]
	[ProducesResponseType<TokenModel>(StatusCodes.Status200OK)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType(StatusCodes.Status409Conflict)]
	public async Task<IActionResult> Create(CreateRequest request, CancellationToken cancellationToken)
	{
		var userId = User.GetUserId();
		var name = TokenName.Create(request.Name);
		var organizationId = new OrganizationId(request.OrganizationId);

		var existingToken = await dataContext.Tokens
			.FirstOrDefaultAsync(x => x.Name == name
				&& x.OrganizationId == organizationId
				&& x.Organization!.Members!.Any(x => x.UserId == userId), cancellationToken);
		if (existingToken is not null)
		{
			return Conflict();
		}

		var organization = await organizationRepository
			.Get(organizationId, userId, cancellationToken);
		if (organization is null)
		{
			return NotFound(nameof(Organization));
		}

		var token = Token.Create(organizationId, name, tokenService.GenerateToken());

		_ = await dataContext.Tokens.AddAsync(token, cancellationToken);
		_ = await dataContext.SaveChangesAsync(cancellationToken);

		var model = TokenModel.FromEntity(token);
		await notificationService.NotifyAdministrators(model,
			"TOKENS.CREATED", cancellationToken);

		return Ok(model);
	}

	[HttpPut("{id:guid}", Name = "UpdateToken")]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	public async Task<IActionResult> Update(Guid id, UpdateRequest request, CancellationToken cancellationToken)
	{
		var userId = User.GetUserId();
		var tokenId = new TokenId(id);

		var token = await dataContext.Tokens
			.FirstOrDefaultAsync(x => x.Id == tokenId
				&& x.Organization!.Members!.Any(x => x.UserId == userId),
				cancellationToken);
		if (token is null)
		{
			return NotFound(nameof(Token));
		}

		token.Update(TokenName.Create(request.Name));

		_ = await dataContext.SaveChangesAsync(cancellationToken);

		await notificationService.NotifyAdministrators(TokenModel.FromEntity(token),
			"TOKENS.UPDATED", cancellationToken);

		return NoContent();
	}

	[HttpPut("{id:guid}/regenerate", Name = "RegenerateToken")]
	[ProducesResponseType<string>(StatusCodes.Status200OK)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	public async Task<IActionResult> Regenerate(Guid id, CancellationToken cancellationToken)
	{
		var userId = User.GetUserId();
		var tokenId = new TokenId(id);

		var token = await dataContext.Tokens
		   .FirstOrDefaultAsync(x => x.Id == tokenId
				&& x.Organization!.Members!.Any(x => x.UserId == userId),
				cancellationToken);
		if (token is null)
		{
			return NotFound(nameof(Token));
		}

		token.NewCode(tokenService.GenerateToken());

		_ = await dataContext.SaveChangesAsync(cancellationToken);

		await notificationService.NotifyAdministrators(TokenModel.FromEntity(token),
			"TOKENS.UPDATED", cancellationToken);

		return Ok(token.Code);
	}

	[HttpDelete("{id:guid}", Name = "DeleteToken")]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	public async Task<IActionResult> Delete(Guid id, CancellationToken cancellationToken)
	{
		var userId = User.GetUserId();
		var tokenId = new TokenId(id);

		var token = await dataContext.Tokens
		   .FirstOrDefaultAsync(x => x.Id == tokenId
				&& x.Organization!.Members!.Any(x => x.UserId == userId),
				cancellationToken);
		if (token is null)
		{
			return NotFound(nameof(Token));
		}

		_ = dataContext.Tokens.Remove(token);

		_ = await dataContext.SaveChangesAsync(cancellationToken);

		await notificationService.NotifyAdministrators(TokenModel.FromEntity(token),
			"TOKENS.DELETED", cancellationToken);

		return NoContent();
	}
}
