using System;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data;
using AsbCloudApp.Data.DailyReport;
using AsbCloudApp.Data.DailyReport.Blocks.Sign;
using AsbCloudApp.Data.DailyReport.Blocks.Subsystems;
using AsbCloudApp.Data.DailyReport.Blocks.TimeBalance;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using AsbCloudApp.Services.DailyReport;
using AsbCloudDb.Model;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace AsbCloudWebApi.Controllers;

/// <summary>
/// Суточный отчёт
/// </summary>
[ApiController]
[Route("api/well/{idWell:int}/[controller]")]
[Authorize]
public class DailyReportController : ControllerBase
{
   private readonly IWellOperationRepository wellOperationRepository;
   private readonly IDailyReportService dailyReportService;
   private readonly IDailyReportExportService dailyReportExportService;
   private readonly IWellService wellService;

   public DailyReportController(IWellOperationRepository wellOperationRepository,
      IDailyReportService dailyReportService,
      IDailyReportExportService dailyReportExportService,
      IWellService wellService)
   {
      this.wellOperationRepository = wellOperationRepository;
      this.dailyReportService = dailyReportService;
      this.dailyReportExportService = dailyReportExportService;
      this.wellService = wellService;
   }

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

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

         return idUser.Value;
      }
   }

   /// <summary>
   /// Обновить подпись
   /// </summary>
   /// <param name="idWell">Id скважины</param>
   /// <param name="dateDailyReport">Дата суточного отчёта</param>
   /// <param name="signBlock">Обновляемый блок</param>
   /// <param name="cancellationToken"></param>
   /// <returns></returns>
   [HttpPut("sign")]
   [Permission]
   [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
   [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
   public Task<IActionResult> UpdateSignBlockAsync(int idWell, DateOnly dateDailyReport, SignBlockDto signBlock,
      CancellationToken cancellationToken) =>
      UpdateOrInsertAsync(idWell, dateDailyReport, signBlock, cancellationToken);

   /// <summary>
   /// Обновить наработку подсистем
   /// </summary>
   /// <param name="idWell">Id скважины</param>
   /// <param name="dateDailyReport">Дата суточного отчёта</param>
   /// <param name="subsystemBlock">Обновляемый блок</param>
   /// <param name="cancellationToken"></param>
   /// <returns></returns>
   [HttpPut("subsystem")]
   [Permission]
   [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
   [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
   public Task<IActionResult> UpdateSubsystemBlockAsync(int idWell, DateOnly dateDailyReport, SubsystemBlockDto subsystemBlock,
      CancellationToken cancellationToken)
   {
      var validSubsystemNames = new[] { "АвтоСПО", "Автопроработка" };

      if (subsystemBlock.Subsystems.Any(m => !validSubsystemNames.Contains(m.Name)))
         throw new ArgumentInvalidException($"Возможно добавить подсистемы с именами {string.Join(", ", validSubsystemNames)}",
            nameof(subsystemBlock.Subsystems));

      return UpdateOrInsertAsync(idWell, dateDailyReport, subsystemBlock, cancellationToken);
   }

   /// <summary>
   /// Обновить баланс времени
   /// </summary>
   /// <param name="idWell">Id скважины</param>
   /// <param name="dateDailyReport">Дата суточного отчёта</param>
   /// <param name="timeBalanceBlock">Обновляемый блок</param>
   /// <param name="cancellationToken"></param>
   /// <returns></returns>
   [HttpPut("timeBalance")]
   [Permission]
   [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
   [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
   public Task<IActionResult> UpdateTimeBalanceBlockAsync(int idWell, DateOnly dateDailyReport, TimeBalanceBlockDto timeBalanceBlock,
      CancellationToken cancellationToken)
   {
      var validWellOperationsIds = new[] { 1, 2, 3, 4 };

      if (timeBalanceBlock.WellOperations.Any(o => !validWellOperationsIds.Contains(o.IdWellOperation)))
         throw new ArgumentInvalidException($"Возможно добавить операции только с Id: {string.Join(", ", validWellOperationsIds)}",
            nameof(timeBalanceBlock.WellOperations));

      var wellSections = wellOperationRepository.GetSectionTypes();

      if (wellSections.All(s => s.Id != timeBalanceBlock.IdSection))
         throw new ArgumentInvalidException($"Секция с Id: {timeBalanceBlock.IdSection} не найдена", nameof(timeBalanceBlock.IdSection));

      return UpdateOrInsertAsync(idWell, dateDailyReport, timeBalanceBlock, cancellationToken);
   }

   /// <summary>
   /// Получить список суточных отчётов по скважине
   /// </summary>
   /// <param name="idWell">Идентификатор скважины</param>
   /// <param name="request">Параметры запроса</param>
   /// <param name="cancellationToken"></param>
   /// <returns></returns>
   [HttpGet]
   [ProducesResponseType(typeof(PaginationContainer<DailyReportDto>), StatusCodes.Status200OK)]
   [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
   public async Task<IActionResult> GetAsync(int idWell, [FromQuery] FileReportRequest request, CancellationToken cancellationToken)
   {
      await AssertUserAccessToWell(idWell, cancellationToken);

      var dailyReports = await dailyReportService.GetAsync(idWell, request, cancellationToken);

      return Ok(dailyReports);
   }
   
   /// <summary>
   /// Получить диапазон дат по которым возможно сформировать суточный отчёты
   /// </summary>
   /// <param name="idWell">Id скважины</param>
   /// <param name="cancellationToken"></param>
   /// <returns></returns>
   [HttpGet("datesRange")]
   [ProducesResponseType(typeof(DatesRangeDto), (int)HttpStatusCode.OK)]
   public async Task<IActionResult> GetDatesRangeAsync(int idWell, CancellationToken cancellationToken)
   {
      await AssertUserAccessToWell(idWell, cancellationToken);

      var datesRanges = await wellOperationRepository.GetDatesRangeAsync(idWell, WellOperation.IdOperationTypeFact, cancellationToken);

      return Ok(datesRanges);
   }
   
   /// <summary>
   /// Экспорт суточного рапорта
   /// </summary>
   /// <param name="idWell">Id скважины</param>
   /// <param name="dateDailyReport">Дата формирования суточного отчёта</param>
   /// <param name="cancellationToken"></param>
   /// <returns></returns>
   [HttpGet("{dateDailyReport}")]
   [ProducesResponseType(typeof(PhysicalFileResult), StatusCodes.Status200OK)]
   [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
   public async Task<IActionResult> ExportAsync(int idWell, DateOnly dateDailyReport, CancellationToken cancellationToken)
   {
      await AssertUserAccessToWell(idWell, cancellationToken);

      var dailyReport = await dailyReportExportService.ExportAsync(idWell, dateDailyReport, cancellationToken);

      return File(dailyReport.File, "application/octet-stream", dailyReport.FileName);
   }

   private async Task<IActionResult> UpdateOrInsertAsync<TBlock>(int idWell, DateOnly dateDailyReport, TBlock block,
      CancellationToken cancellationToken)
      where TBlock : ItemInfoDto
   {
      await AssertUserAccessToWell(idWell, cancellationToken);
      
      var id = await dailyReportService.UpdateOrInsertAsync(idWell, dateDailyReport, IdUser, block, cancellationToken);

      return Ok(id);
   }

   private async Task AssertUserAccessToWell(int idWell, CancellationToken cancellationToken)
   {
      var idCompany = User.GetCompanyId();

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