using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Monitoring.Common;
using Monitoring.Core.Domain.ServiceOrganizationManagement;
using Monitoring.Extensions;
using Monitoring.Models;
using Monitoring.Features.ServiceOrganization.GetServiceOrganizationList;
using Monitoring.Features.ServiceOrganization.GetServiceOrganizationById;
using Monitoring.Features.ServiceOrganization.CreateServiceOrganization;
using Monitoring.Features.ServiceOrganization.UpdateServiceOrganization;
using Monitoring.Infrastructure.Data;
using Monitoring.Services;

namespace Monitoring.Features.ServiceOrganization
{
    [ApiController]
    [Route("api/service-organizations")]
    [Authorize(Roles = $"{Roles.Manager},{Roles.Watcher}")]
    public class ServiceOrganizationController : ControllerBase
    {
        private readonly DataContext _dataContext;
        private readonly INotificationService _notificationService;

        public ServiceOrganizationController(
            DataContext dataContext,
            INotificationService notificationService)
        {
            _dataContext = dataContext;
            _notificationService = notificationService;
        }

        [HttpGet]
        public async Task<ActionResult<List<ServiceOrganizationModel>>> GetList(
            [FromQuery] GetServiceOrganizationListRequest request,
            CancellationToken cancellationToken)
        { 
	        return Ok(await _dataContext.ServiceOrganizations
                .GetTable(request, ServiceOrganizationModel.Projection, cancellationToken));
        }

        [HttpGet("{id:guid}")]
        public async Task<ActionResult<ServiceOrganizationModel>> GetById(
            [FromRoute] GetServiceOrganizationByIdRequest request,
            CancellationToken cancellationToken)
        {
            var serviceOrganizationId = new ServiceOrganizationId(request.Id);
            var model = await _dataContext.ServiceOrganizations
                .Where(x => x.Id == serviceOrganizationId)
                .Select(ServiceOrganizationModel.Projection)
                .FirstOrDefaultAsync(cancellationToken);
                
            return model is null ? NotFound() : Ok(model);
        }

        [HttpPost]
        [Authorize(Roles = Roles.Manager)]
        public async Task<IActionResult> Create(
	        [FromBody] CreateServiceOrganizationRequest request,
	        CancellationToken cancellationToken)
        {
	        var name = ServiceOrganizationName.Create(request.Name);
            
	        var existingOrganization = await _dataContext.ServiceOrganizations
		        .FirstOrDefaultAsync(o => o.Name.Value == name.Value, cancellationToken);
                
	        if (existingOrganization is not null)
	        {
		        return Conflict();
	        }
            
	        var organization = Core.Domain.ServiceOrganizationManagement.ServiceOrganization.Create(name);
            if (organization.TimeStamp == null || organization.TimeStamp.Length == 0)
            {
                organization.TimeStamp = Guid.NewGuid().ToByteArray();
            }
            
	        _ = await _dataContext.ServiceOrganizations.AddAsync(organization, cancellationToken);
	        _ = await _dataContext.SaveChangesAsync(cancellationToken);

	        var model = await _dataContext.ServiceOrganizations
		        .Where(x => x.Id == organization.Id)
		        .Select(ServiceOrganizationModel.Projection)
		        .FirstAsync(cancellationToken);
            
	        await _notificationService.NotifyAdministrators(
		        model,
		        "SERVICE_ORGANIZATIONS.CREATED",
		        cancellationToken);
            
	        return Ok(model);
        }

        [HttpPut("{id:guid}")]
        [Authorize(Roles = Roles.Manager)]
        public async Task<IActionResult> Update(
	        Guid id, 
	        [FromBody] UpdateServiceOrganizationRequest request,
	        CancellationToken cancellationToken)
        {

	        var serviceOrganizationId = new ServiceOrganizationId(id);
	        var organization = await _dataContext.ServiceOrganizations
		        .FirstOrDefaultAsync(o => o.Id == serviceOrganizationId, cancellationToken);
            
	        if (organization is null)
	        {
		        return NotFound();
	        }

	        var nameExists = await _dataContext.ServiceOrganizations
		        .AnyAsync(o => o.Id != serviceOrganizationId && o.Name.Value == request.Name, cancellationToken);
                
	        if (nameExists)
	        {
		        return Conflict();
	        }

	        organization.Name = ServiceOrganizationName.Create(request.Name);
	        organization.UpdatedAt = DateTime.UtcNow;

	        _ = await _dataContext.SaveChangesAsync(cancellationToken);

	        await _notificationService.NotifyAdministrators(
		        organization,
		        "SERVICE_ORGANIZATIONS.UPDATED", 
		        cancellationToken);

	        return NoContent();
        }

        [HttpDelete("{id:guid}")]
        [Authorize(Roles = Roles.Manager)]
        public async Task<IActionResult> Delete(
	        Guid id,
	        CancellationToken cancellationToken)
        {
	        var serviceOrganizationId = new ServiceOrganizationId(id);
	        var organization = await _dataContext.ServiceOrganizations
		        .FirstOrDefaultAsync(o => o.Id == serviceOrganizationId, cancellationToken);
                
	        if (organization is null)
	        {
		        return NotFound();
	        }

            var usedAssets = await _dataContext.Assets
                .Where(a => a.ServiceOrganizationId == organization.Id)
                .Select(a => a.Id.Value)
                .ToListAsync(cancellationToken);

            if (usedAssets.Any())
            {
                return Conflict(new { UsedInAssets = usedAssets });
            }

	        _ = _dataContext.ServiceOrganizations.Remove(organization);
	        _ = await _dataContext.SaveChangesAsync(cancellationToken);

	        await _notificationService.NotifyAdministrators(
		        organization,
		        "SERVICE_ORGANIZATIONS.DELETED",
		        cancellationToken);

	        return NoContent();
        }
    }
}