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.RolePrimitives;
using Monitoring.Core.Domain.UserPrimitives;
using Monitoring.Extensions;
using Monitoring.Features.Users.CreateUser;
using Monitoring.Features.Users.GetUserList;
using Monitoring.Features.Users.UpdateUser;
using Monitoring.Infrastructure.Data;
using Monitoring.Infrastructure.Services;
using Monitoring.Models;
using Monitoring.Repositories;
using Monitoring.Services;

using Serilog;

namespace Monitoring.Features.Users;

[Route("api/users")]
[Authorize(Roles = Roles.Administrator)]
public class UsersController(
	DataContext dataContext,
	INotificationService notificationService,
	PasswordService passwordService,
	RoleRepository roleRepository,
	UserRepository userRepository
) : BaseController
{
	[HttpGet(Name = "GetUserList")]
	[ProducesResponseType<TableDataResponseModel<UserModel>>(StatusCodes.Status200OK)]
	public async Task<IActionResult> GetUsers(
		[FromQuery] GetUserListRequest request,
		CancellationToken cancellationToken
	)
	{
		Log.Information("Try to get list of users");

		IQueryable<User> users = dataContext.Users
			.Include(x => x.Role!);

		if (request.Roles is not null)
		{
			Log.Information("Get user list by roles: {roles}", request.Roles);
			var roles = request.Roles.Split(',').Select(RoleUpperCaseName.Create);

			users = users.Where(x => roles.Contains(x.Role!.UpperCaseName));
		}

		return Ok(await users
			.Select(x => UserModel.FromEntity(x))
			.GetTable(request, cancellationToken));
	}

	[HttpGet("{id:guid}", Name = "GetUser")]
	[ProducesResponseType<UserModel>(StatusCodes.Status200OK)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	public async Task<IActionResult> Get(
		Guid id,
		CancellationToken cancellationToken
	)
	{
		var userId = new UserId(id);
		var user = await dataContext.Users
		   .Include(x => x.Role)
		   .FirstOrDefaultAsync(x => x.Id == userId, cancellationToken);
		return user is null ? NotFound(nameof(User)) : Ok(UserModel.FromEntity(user));
	}

	[HttpPost(Name = "CreateUser")]
	[ProducesResponseType<UserModel>(StatusCodes.Status200OK)]
	[ProducesResponseType(StatusCodes.Status409Conflict)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	public async Task<IActionResult> CreateUser(
		[FromBody] CreateUserRequest request,
		CancellationToken cancellationToken
	)
	{
		Log.Information("Try to create user: {username}", request.Username);
		var user = await userRepository.Find(request.Username, cancellationToken);
		if (user is not null)
		{
			return Conflict();
		}

		var role = await roleRepository.Find(request.Role, cancellationToken);
		if (role is null)
		{
			return NotFound(nameof(Role));
		}

		var username = UserName.Create(request.Username);
		var password = Password.Create(request.Password);
		user = Core.Domain.User.Create(username, passwordService.Hash(password), role.Id);

		_ = await dataContext.Users.AddAsync(user, cancellationToken);
		_ = await dataContext.SaveChangesAsync(cancellationToken);

		await notificationService.NotifyAdministrators(UserModel.FromEntity(user),
			"USERS.CREATED", cancellationToken);

		return Ok(UserModel.FromEntity(user));
	}

	[HttpPut("{id:guid}", Name = "UpdateUser")]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType(StatusCodes.Status409Conflict)]
	public async Task<IActionResult> UpdateUser(
		Guid id,
		[FromBody] UpdateUserRequest request,
		CancellationToken cancellationToken
	)
	{
		var userId = new UserId(id);
		var user = await userRepository.Get(userId, cancellationToken);
		if (user is null)
		{
			return NotFound(nameof(User));
		}

		var role = await roleRepository.Find(request.Role, cancellationToken);
		if (role is null)
		{
			return NotFound(nameof(Role));
		}

		var username = UserName.Create(request.Username);
		var upperCaseUsername = UserUpperCaseName.Create(request.Username);
		var sameUsernameUser = await dataContext.Users
			.FirstOrDefaultAsync(x => x.UpperCaseUsername == upperCaseUsername, cancellationToken);
		if (sameUsernameUser is not null && sameUsernameUser.Id != user.Id)
		{
			return Conflict();
		}

		var password = Password.Create(request.Password);
		var passwordHash = passwordService.Hash(password);
		user.Update(username, passwordHash, role.Id);

		_ = await dataContext.SaveChangesAsync(cancellationToken);

		await notificationService.NotifyAdministrators(UserModel.FromEntity(user),
			"USERS.UPDATED", cancellationToken);

		return NoContent();
	}

	[HttpDelete("{id:guid}", Name = "DeleteUser")]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	public async Task<IActionResult> DeleteUser(
		Guid id,
		CancellationToken cancellationToken
	)
	{
		Log.Information("Try to delete user: {id}", id);

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

		_ = dataContext.Users.Remove(user);

		_ = await dataContext.SaveChangesAsync(cancellationToken);

		Log.Information("User is deleted: {id}", id);

		await notificationService.NotifyAdministrators(UserModel.FromEntity(user),
			"USERS.DELETED", cancellationToken);

		return NoContent();
	}
}