using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudApp.Services.ProcessMaps;
using AsbCloudWebApi.SignalR;
using AsbCloudWebApi.SignalR.Clients;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;

namespace AsbCloudWebApi.Controllers.ProcessMaps;

/// <summary>
/// РТК
/// </summary>
[ApiController]
[Route("api/well/{idWell}/[controller]")]
[Authorize]
public abstract class ProcessMapBaseController<T> : ControllerBase
    where T : ProcessMapPlanBaseDto
{
    private readonly IHubContext<TelemetryHub, ITelemetryHubClient> telemetryHubContext;
    private readonly ITelemetryService telemetryService;
    private readonly IWellService wellService;
    private readonly IUserRepository userRepository;
    private readonly ICrudRepository<WellSectionTypeDto> wellSectionRepository;
    private readonly IProcessMapPlanRepository<T> repository;
    private readonly IProcessMapPlanService<T> service;

    protected ProcessMapBaseController(IWellService wellService,
        IProcessMapPlanRepository<T> repository,
        IUserRepository userRepository,
        ICrudRepository<WellSectionTypeDto> wellSectionRepository,
        IHubContext<TelemetryHub, ITelemetryHubClient> telemetryHubContext,
        ITelemetryService telemetryService,
        IProcessMapPlanService<T> service)
    {
        this.wellService = wellService;
        this.repository = repository;
        this.userRepository = userRepository;
        this.wellSectionRepository = wellSectionRepository;
        this.telemetryHubContext = telemetryHubContext;
        this.telemetryService = telemetryService;
        this.service = service;
    }

    protected abstract string SignalRGroup { get; }

    protected int IdUser
    {
        get
        {
            var idUser = User.GetUserId();

            if (!idUser.HasValue)
                throw new ForbidException("Неизвестный пользователь");

            return idUser.Value;
        }
    }
    
    /// <summary>
    /// Создание плановой РТК
    /// </summary>
    /// <param name="processMap">Тело запроса</param>
    /// <param name="idWell">Id скважины</param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [HttpPost]
    [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public virtual async Task<IActionResult> InsertAsync(T processMap, int idWell, CancellationToken cancellationToken)
    {
        processMap.IdWell = idWell;
        processMap.IdUser = IdUser;
        processMap.LastUpdate = DateTime.UtcNow;

        await CheckIsExistsWellSectionTypeAsync(processMap.IdWellSectionType, cancellationToken);

        await AssertUserHasAccessToEditProcessMapAsync(processMap.IdWell, cancellationToken);

        var result = await repository.InsertAsync(processMap, cancellationToken);

        await NotifyUsersBySignalR(idWell, cancellationToken);

        return Ok(result);
    }

    /// <summary>
    /// Обновление плановой РТК
    /// </summary>
    /// <param name="processMap">Тело запроса</param>
    /// <param name="idWell">Id скважины</param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [HttpPut]
    [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public virtual async Task<IActionResult> UpdateAsync(T processMap, int idWell, CancellationToken cancellationToken)
    {
        processMap.IdWell = idWell;
        processMap.IdUser = IdUser;
        processMap.LastUpdate = DateTime.UtcNow;

        await CheckIsExistsWellSectionTypeAsync(processMap.IdWellSectionType, cancellationToken);

        await AssertUserHasAccessToEditProcessMapAsync(idWell, cancellationToken);

        var result = await repository.UpdateAsync(processMap, cancellationToken);

        if (result == ICrudRepository<T>.ErrorIdNotFound)
            return this.ValidationBadRequest(nameof(processMap.Id), $"РТК с Id: {processMap.Id} не существует");

        await NotifyUsersBySignalR(idWell, cancellationToken);

        return Ok(result);
    }

    /// <summary>
    /// Удаление плановой РТК
    /// </summary>
    /// <param name="id">Id удаляемой РТК</param>
    /// <param name="idWell">Id скважины</param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [HttpDelete]
    [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
    public virtual async Task<IActionResult> DeleteAsync(int id, int idWell, CancellationToken cancellationToken)
    {
        await AssertUserHasAccessToEditProcessMapAsync(idWell, cancellationToken);

        var result = await repository.DeleteAsync(id, cancellationToken);

        await NotifyUsersBySignalR(idWell, cancellationToken);

        return Ok(result);
    }

    /// <summary>
    /// Получение РТК по Id скважины
    /// </summary>
    /// <param name="idWell">Id скважины</param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [HttpGet]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    [ProducesResponseType(StatusCodes.Status403Forbidden)]
    public async Task<ActionResult<IEnumerable<ValidationResultDto<T>>>> GetAsync(int idWell, CancellationToken cancellationToken)
    {
        var processMaps = await service.GetAsync(idWell, cancellationToken);

        if (!processMaps.Any())
            return NoContent();
        
        return Ok(processMaps);
    }

    /// <summary>
    /// Получение РТК по телеметрии
    /// </summary>
    /// <param name="uid">Уникальный ключ телеметрии</param>
    /// <param name="updateFrom">Дата с которой требуется получить РТК</param>
    /// <param name="cancellationToken"></param>
    /// <returns></returns>
    [HttpGet("/api/telemetry/{uid}/[controller]")]
    [AllowAnonymous]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<IEnumerable<T>>> GetProcessMapPlanByTelemetry(string uid, DateTime updateFrom, CancellationToken cancellationToken)
    {
        var idWell = telemetryService.GetIdWellByTelemetryUid(uid);

        if (!idWell.HasValue)
            return this.ValidationBadRequest(nameof(uid), $"Wrong uid {uid}");

        var requests = new[] { new ProcessMapPlanRequest
           {
              IdWell = idWell.Value,
              UpdateFrom = updateFrom
           }
       };

        var processMaps = await repository.GetAsync(requests, cancellationToken);

        return Ok(processMaps);
    }

    protected async Task AssertUserHasAccessToEditProcessMapAsync(int idWell, CancellationToken cancellationToken)
    {
        var well = await wellService.GetOrDefaultAsync(idWell, cancellationToken)
                    ?? throw new ArgumentInvalidException(nameof(idWell), $"Скважины с {idWell} не существует");

        var idCompany = User.GetCompanyId();

        if (!idCompany.HasValue ||
           !await wellService.IsCompanyInvolvedInWellAsync(idCompany.Value, idWell, cancellationToken))
            throw new ForbidException("Нет доступа к скважине");

        if (well.IdState == 2 && !userRepository.HasPermission(IdUser, "ProcessMap.editCompletedWell"))
            throw new ForbidException("Недостаточно прав для редактирования РТК завершенной скважины");
    }

    protected async Task NotifyUsersBySignalR(int idWell, CancellationToken cancellationToken)
    {
        var dtos = await repository.GetByIdWellAsync(idWell, cancellationToken);

        await telemetryHubContext.Clients
           .Group($"{SignalRGroup}_{idWell}")
           .UpdateProcessMap(dtos, cancellationToken);
    }

    private async Task CheckIsExistsWellSectionTypeAsync(int idWellSectionType, CancellationToken cancellationToken)
    {
        _ = await wellSectionRepository.GetOrDefaultAsync(idWellSectionType, cancellationToken)
            ?? throw new ArgumentInvalidException(nameof(ProcessMapPlanWellDrillingDto.IdWellSectionType), $"Тип секции с Id: {idWellSectionType} не найден");
    }
}