using System.Linq;
using CSharpFunctionalExtensions;
using FluentValidation;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Monitoring.Common;
using Monitoring.Controllers;
using Monitoring.Core.Domain.CheckListManagement;
using Monitoring.Core.Domain.EventManagement;
using Monitoring.Core.Domain.UserLogManagement;
using Monitoring.Infrastructure.Data;
using Monitoring.Repositories;

namespace Monitoring.Features.AlarmManagement;

[Route("api/alarm-scopes/{alarmScopeId}/statuses")]
[Authorize(Roles = Roles.Watcher)]
public class AlarmScopeStatusController(
	DataContext dataContext,
	UserLogRepository userLogRepository,
	AlarmScopeTransitionMatrix alarmScopeTransitionMatrix
) : BaseController
{
	/// <summary>
	/// Следующий статус группы тревог.
	/// </summary>
	/// <param name="alarmScopeId">ИД группы тревог.</param>
	/// <param name="request">Запрос.</param>
	/// <param name="cancellationToken">Токен отмены.</param>
	[HttpPut("", Name = "NextAlarmScopeStatus")]
	[ProducesResponseType<AlarmScopeStatusChangingModel>(StatusCodes.Status200OK)]
	[ProducesResponseType<string>(StatusCodes.Status404NotFound)]
	[ProducesResponseType<string>(StatusCodes.Status400BadRequest)]
	public async Task<IActionResult> NextStatus(
		Guid alarmScopeId,
		NextAlarmScopeStatusRequest request,
		CancellationToken cancellationToken
	)
	{
		var alarmScopeIdValue = new AlarmScopeId(alarmScopeId);

		// Одним запросом: и существование, и доступ
		var alarmScope = await dataContext
			.AlarmScopes.Include(x => x.Asset!)
			.Include(a => a.StatusHistory)
			.Where(a =>
				a.Asset!.Groups.Any(g => g.Accesses.Any(acc => acc.Member.UserId == UserId))
			)
			.FirstOrDefaultAsync(a => a.Id == alarmScopeIdValue, cancellationToken);
		if (alarmScope is null)
		{
			return NotFound("AlarmScope not found");
		}

		var currentStatus = alarmScope.StatusHistory.OrderByDescending(s => s.CreatedAt).First();
		currentStatus ??= await SaveStatusToDatabase(
			alarmScopeIdValue,
			AlarmScopeStatus.New,
			cancellationToken
		);

		if (currentStatus.Status == AlarmScopeStatus.Resolved)
		{
			return BadRequest("Alarm scope already resolved");
		}

		var hasCameras = await dataContext.Cameras.AnyAsync(
			x => x.AssetId == alarmScope.AssetId,
			cancellationToken
		);
		var hasResponsiblePersons = await dataContext.ResponsiblePersons.AnyAsync(
			x => x.AssetId == alarmScope.AssetId,
			cancellationToken
		);

		var nextStatusResult = await alarmScopeTransitionMatrix.GetTransitionMatrix(
			alarmScope,
			UserId
		);
		if (nextStatusResult.IsFailure)
		{
			return BadRequest(nextStatusResult.Error);
		}

		var strategy = dataContext.Database.CreateExecutionStrategy();

		return await strategy.ExecuteAsync(async () =>
		{
			await using var transaction = await dataContext.Database.BeginTransactionAsync(
				cancellationToken
			);

			var nextStatus = nextStatusResult.Value;
			var nextStatusModel = await SaveStatusToDatabase(
				alarmScopeIdValue,
				nextStatus,
				cancellationToken
			);
			await dataContext.SaveChangesAsync(cancellationToken);

			await userLogRepository.Create(
				alarmScope.Asset!.Id,
				UserId,
				UserLogInformation.Create("Alarm scope status changed to " + nextStatus),
				cancellationToken
			);

			if (nextStatus == AlarmScopeStatus.CommentAndSend)
			{
				var result =
					request.IsReal
					?? await dataContext.Calls.AnyAsync(x =>
						x.AlarmScopeId == alarmScope.Id && x.IsConfirmedRealAlarm
					);

				alarmScope.SetResult(result ? AlarmScopeResult.Real : AlarmScopeResult.False);
				await dataContext.SaveChangesAsync(cancellationToken);

				await CreateCheckList(
					result ? CheckListType.ByRealAlarm : CheckListType.ByFalseAlarm,
					alarmScope,
					cancellationToken
				);
			}

			await transaction.CommitAsync(cancellationToken);

			return Ok(AlarmScopeStatusChangingModel.Projection.Compile()(nextStatusModel));
		});
	}

	private async Task<Result> CreateCheckList(
		CheckListType checkListType,
		AlarmScope alarmScope,
		CancellationToken cancellationToken
	)
	{
		var checkListTemplate = await dataContext
			.CheckListTemplates.Include(x => x.Items)
			.FirstOrDefaultAsync(x => x.Type == checkListType, cancellationToken);
		if (checkListTemplate is null)
		{
			return Result.Failure("Check list template not found");
		}

		var checkList = CheckList.Create(checkListTemplate.Id, alarmScope.Id);
		await dataContext.CheckLists.AddAsync(checkList, cancellationToken);
		await dataContext.SaveChangesAsync(cancellationToken);

		foreach (var item in checkListTemplate.Items)
		{
			await dataContext.CheckListItems.AddAsync(
				CheckListItem.Create(checkList.Id, item.Id),
				cancellationToken
			);
		}
		await dataContext.SaveChangesAsync(cancellationToken);

		return Result.Success();
	}

	private async Task<AlarmScopeStatusChanging> SaveStatusToDatabase(
		AlarmScopeId alarmScopeIdValue,
		AlarmScopeStatus status,
		CancellationToken cancellationToken
	)
	{
		var currentStatus = AlarmScopeStatusChanging.Create(alarmScopeIdValue, UserId, status);
		await dataContext.AlarmScopeStatusHistory.AddAsync(currentStatus, cancellationToken);
		await dataContext.SaveChangesAsync(cancellationToken);
		return currentStatus;
	}
}
