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.EventManagement;
using Monitoring.Core.Domain.FireDepartmentManagement;
using Monitoring.Core.Domain.UserPrimitives;
using Monitoring.Extensions;
using Monitoring.Features.AlarmFireDepartmentDispatches.CreateDispatch;
using Monitoring.Features.AlarmFireDepartmentDispatches.GetDispatchList;
using Monitoring.Features.AlarmFireDepartmentDispatches.UpdateDispatch;
using Monitoring.Infrastructure.Data;
using Monitoring.Models;

namespace Monitoring.Features.AlarmFireDepartmentDispatches;

[Route("api/alarm-dispatches")]
[Authorize(Roles = Roles.Watcher)]
public class AlarmFireDepartmentDispatchesController(DataContext dataContext) : BaseController
{
	[HttpGet(Name = "GetAlarmDispatchList")]
	[ProducesResponseType<TableDataResponseModel<AlarmFireDepartmentDispatchModel>>(
		StatusCodes.Status200OK
	)]
	public async Task<IActionResult> GetList(
		[FromQuery] GetDispatchListRequest request,
		CancellationToken cancellationToken
	)
	{
		var query = dataContext
			.AlarmFireDepartmentDispatches.Include(d => d.Alarm)
			.ThenInclude(a => a.Event)
			.ThenInclude(e => e.Asset)
			.Where(d => d.Alarm.Event.Asset.Organization.Members.Any(m => m.UserId == UserId));

		// только свои по умолчанию
		if (request.OnlyMine)
		{
			query = query.Where(d => d.SenderId == UserId);
		}

		// оператор должен иметь доступ к группе актива
		query = query.Where(d =>
			d.Alarm.Event.Asset.Groups.Any(g => g.Accesses.Any(a => a.Member.UserId == UserId))
		);

		if (request.AlarmId.HasValue)
		{
			var alarmId = new AlarmId(request.AlarmId.Value);
			query = query.Where(d => d.AlarmId == alarmId);
		}
		if (request.DepartmentId.HasValue)
		{
			var deptId = new FireDepartmentId(request.DepartmentId.Value);
			query = query.Where(d => d.DepartmentId == deptId);
		}

		var list = await query
			.AsNoTracking()
			.OrderByDescending(d => d.SentAt)
			.Select(AlarmFireDepartmentDispatchModel.FromEntity)
			.GetTable(request, cancellationToken);

		return Ok(list);
	}

	[HttpGet("{id:guid}", Name = "GetAlarmDispatch")]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<AlarmFireDepartmentDispatchModel>(StatusCodes.Status200OK)]
	public async Task<IActionResult> Get(Guid id, CancellationToken cancellationToken)
	{
		var dispId = new AlarmFireDepartmentDispatchId(id);

		var exists = await dataContext
			.AlarmFireDepartmentDispatches.Include(d => d.Alarm)
			.ThenInclude(a => a.Event)
			.ThenInclude(e => e.Asset)
			.AnyAsync(
				d =>
					d.Id == dispId
					&& d.Alarm.Event.Asset.Organization.Members.Any(m => m.UserId == UserId),
				cancellationToken
			);
		if (!exists)
		{
			return NotFound("Dispatch not found");
		}

		var hasAccess = await dataContext
			.AlarmFireDepartmentDispatches.Where(d => d.Id == dispId)
			.AnyAsync(
				d =>
					d.Alarm.Event.Asset.Groups.Any(g =>
						g.Accesses.Any(a => a.Member.UserId == UserId)
					),
				cancellationToken
			);
		if (!hasAccess)
		{
			return NotFound("Dispatch not found");
		}

		var model = await dataContext
			.AlarmFireDepartmentDispatches.Where(d => d.Id == dispId)
			.Select(AlarmFireDepartmentDispatchModel.FromEntity)
			.FirstAsync(cancellationToken);
		return Ok(model);
	}

	[HttpPost(Name = "CreateAlarmDispatch")]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<string>(StatusCodes.Status409Conflict)]
	[ProducesResponseType<AlarmFireDepartmentDispatchModel>(StatusCodes.Status200OK)]
	public async Task<IActionResult> Create(
		CreateDispatchRequest request,
		CancellationToken cancellationToken
	)
	{
		// доступ к тревоге
		var alarmId = new AlarmId(request.AlarmId);
		var alarm = await dataContext
			.Alarms.Include(a => a.Event)
			.ThenInclude(e => e.Asset)
			.ThenInclude(a => a.Organization)
			.FirstOrDefaultAsync(
				a =>
					a.Id == alarmId
					&& a.Event.Asset.Organization.Members.Any(m => m.UserId == UserId),
				cancellationToken
			);
		if (alarm is null)
		{
			return NotFound("Alarm not found");
		}
		var hasAssetAccess = await dataContext
			.Alarms.Where(a => a.Id == alarmId)
			.AnyAsync(
				a =>
					a.Event.Asset.Groups.Any(g => g.Accesses.Any(ac => ac.Member.UserId == UserId)),
				cancellationToken
			);
		if (!hasAssetAccess)
		{
			return NotFound("Alarm not found");
		}

		// доступ к пожарной части
		var deptId = new FireDepartmentId(request.DepartmentId);
		var deptExists = await dataContext.FireDepartments.AnyAsync(
			fd => fd.Id == deptId,
			cancellationToken
		);
		if (!deptExists)
		{
			return NotFound("Fire department not found");
		}
		var hasDeptAccess = await dataContext.FireDepartmentAccesses.AnyAsync(
			a => a.DepartmentId == deptId && a.UserId == UserId,
			cancellationToken
		);
		if (!hasDeptAccess)
		{
			return NotFound("Fire department not found");
		}

		var dispatcherName = Name.Create(request.DispatcherName);
		var comment = Description.Create(request.Comment);
		var dispatch = AlarmFireDepartmentDispatch.Create(
			alarm.Id,
			UserId,
			deptId,
			dispatcherName,
			comment
		);

		await dataContext.AlarmFireDepartmentDispatches.AddAsync(dispatch, cancellationToken);
		await dataContext.SaveChangesAsync(cancellationToken);

		var model = await dataContext
			.AlarmFireDepartmentDispatches.Where(d => d.Id == dispatch.Id)
			.Select(AlarmFireDepartmentDispatchModel.FromEntity)
			.FirstAsync(cancellationToken);
		return Ok(model);
	}

	[HttpPut("{id:guid}", Name = "UpdateAlarmDispatch")]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	public async Task<IActionResult> Update(
		Guid id,
		UpdateDispatchRequest request,
		CancellationToken cancellationToken
	)
	{
		var dispId = new AlarmFireDepartmentDispatchId(id);
		var dispatch = await dataContext
			.AlarmFireDepartmentDispatches.Include(d => d.Alarm)
			.ThenInclude(a => a.Event)
			.ThenInclude(e => e.Asset)
			.FirstOrDefaultAsync(
				d =>
					d.Id == dispId
					&& d.Alarm.Event.Asset.Organization.Members.Any(m => m.UserId == UserId),
				cancellationToken
			);
		if (dispatch is null)
		{
			return NotFound("Dispatch not found");
		}
		if (dispatch.SenderId != UserId)
		{
			return NotFound("Dispatch not found");
		}

		// проверка доступа к новой ДЧС
		var deptId = new FireDepartmentId(request.DepartmentId);
		var deptExists = await dataContext.FireDepartments.AnyAsync(
			fd => fd.Id == deptId,
			cancellationToken
		);
		if (!deptExists)
		{
			return NotFound("Fire department not found");
		}
		var hasDeptAccess = await dataContext.FireDepartmentAccesses.AnyAsync(
			a => a.DepartmentId == deptId && a.UserId == UserId,
			cancellationToken
		);
		if (!hasDeptAccess)
		{
			return NotFound("Fire department not found");
		}

		var dispatcherName = Name.Create(request.DispatcherName);
		var comment = Description.Create(request.Comment);

		typeof(AlarmFireDepartmentDispatch)
			.GetProperty(nameof(AlarmFireDepartmentDispatch.DepartmentId))!
			.SetValue(dispatch, deptId);
		typeof(AlarmFireDepartmentDispatch)
			.GetProperty(nameof(AlarmFireDepartmentDispatch.DispatcherName))!
			.SetValue(dispatch, dispatcherName);
		typeof(AlarmFireDepartmentDispatch)
			.GetProperty(nameof(AlarmFireDepartmentDispatch.Comment))!
			.SetValue(dispatch, comment);

		await dataContext.SaveChangesAsync(cancellationToken);
		return NoContent();
	}

	[HttpDelete("{id:guid}", Name = "DeleteAlarmDispatch")]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType(StatusCodes.Status204NoContent)]
	public async Task<IActionResult> Delete(Guid id, CancellationToken cancellationToken)
	{
		var dispId = new AlarmFireDepartmentDispatchId(id);
		var dispatch = await dataContext
			.AlarmFireDepartmentDispatches.Include(d => d.Alarm)
			.ThenInclude(a => a.Event)
			.ThenInclude(e => e.Asset)
			.FirstOrDefaultAsync(
				d =>
					d.Id == dispId
					&& d.Alarm.Event.Asset.Organization.Members.Any(m => m.UserId == UserId),
				cancellationToken
			);
		if (dispatch is null)
		{
			return NotFound("Dispatch not found");
		}
		if (dispatch.SenderId != UserId)
		{
			return NotFound("Dispatch not found");
		}

		dataContext.AlarmFireDepartmentDispatches.Remove(dispatch);
		await dataContext.SaveChangesAsync(cancellationToken);
		return NoContent();
	}
}
