using AsbCloudApp.Data;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using AsbCloudApp.Data.WellOperation;
using AsbCloudApp.Requests.ExportOptions;
using AsbCloudApp.Requests.ParserOptions;
using AsbCloudDb.Model;
using AsbCloudInfrastructure.Services.WellOperations.Factories;
using System.Linq;

namespace AsbCloudWebApi.Controllers;

/// <summary>
/// Буровые операции (вводимые вручную)
/// </summary>
[Route("api/well/{idWell}/wellOperations")]
[ApiController]
[Authorize]
public class WellOperationController : ControllerBase
{
    private readonly IDictionary<int, string> templateNames = new Dictionary<int, string>
    {
        { WellOperation.IdOperationTypeFact, "ЕЦП_шаблон_файла_фактические_операции.xlsx" },
        { WellOperation.IdOperationTypePlan, "ЕЦП_шаблон_файла_плановые_операции.xlsx" }
    };

    private readonly IUserRepository userRepository;
    private readonly IWellOperationRepository wellOperationRepository;
    private readonly IWellOperationCategoryRepository wellOperationCategoryRepository;
    private readonly IWellService wellService;

    private readonly WellOperationParserFactory wellOperationParserFactory;
    private readonly WellOperationExportServiceFactory wellOperationExportServiceFactory;

    public WellOperationController(IWellOperationRepository wellOperationRepository,
        IWellOperationCategoryRepository wellOperationCategoryRepository,
        IWellService wellService,
        IUserRepository userRepository,
        WellOperationParserFactory wellOperationParserFactory,
        WellOperationExportServiceFactory wellOperationExportServiceFactory)
    {
        this.wellOperationRepository = wellOperationRepository;
        this.wellOperationCategoryRepository = wellOperationCategoryRepository;
        this.wellService = wellService;
        this.userRepository = userRepository;
        this.wellOperationParserFactory = wellOperationParserFactory;
        this.wellOperationExportServiceFactory = wellOperationExportServiceFactory;
    }

    /// <summary>
    /// Добавляет новые операции на скважине
    /// </summary>
    /// <param name="idWell">Id скважины</param>
    /// <param name="dtos">Добавляемые операции</param>
    /// <param name="deleteBeforeInsert">Удалить операции перед сохранением</param>
    /// <param name="cancellationToken"></param>
    /// <returns>Количество добавленных в БД записей</returns>
    [HttpPost("{deleteBeforeInsert:bool}")]
    [Permission]
    [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> InsertRangeAsync(
        [Range(1, int.MaxValue, ErrorMessage = "Id скважины не может быть меньше 1")]
        int idWell,
        bool deleteBeforeInsert,
        [FromBody] IEnumerable<WellOperationDto> dtos,
        CancellationToken cancellationToken)
    {
        if (!await CanUserAccessToWellAsync(idWell, cancellationToken))
            return Forbid();

        if (!await CanUserEditWellOperationsAsync(idWell, cancellationToken))
            return Forbid();

        foreach (var dto in dtos)
        {
            dto.IdWell = idWell;
            dto.LastUpdateDate = null;
            dto.IdUser = User.GetUserId();
        }

        var result = await wellOperationRepository.InsertRangeAsync(dtos, deleteBeforeInsert, cancellationToken);

        return Ok(result);
    }

    /// <summary>
    /// Обновляет выбранную операцию на скважине
    /// </summary>
    /// <param name="idWell">id скважины</param>
    /// <param name="dtos"></param>
    /// <param name="token">Токен отмены задачи</param>
    /// <returns>Количество обновленных в БД строк</returns>
    [HttpPut]
    [Permission]
    [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
    public async Task<IActionResult> UpdateRangeAsync(int idWell,
        [FromBody] IEnumerable<WellOperationDto> dtos,
        CancellationToken token)
    {
        if (!await CanUserAccessToWellAsync(idWell, token))
            return Forbid();

        if (!await CanUserEditWellOperationsAsync(idWell, token))
            return Forbid();

        foreach (var dto in dtos)
        {
            dto.IdWell = idWell;
            dto.IdUser = User.GetUserId();
            dto.LastUpdateDate = DateTimeOffset.UtcNow;
        }

        var result = await wellOperationRepository.UpdateRangeAsync(dtos, token);

        return Ok(result);
    }

    /// <summary>
    /// Возвращает словарь типов секций
    /// </summary>
    /// <returns></returns>
    [HttpGet("sectionTypes")]
    [Permission]
    [ProducesResponseType(typeof(IEnumerable<WellSectionTypeDto>), StatusCodes.Status200OK)]
    public IActionResult GetSectionTypes()
    {
        var result = wellOperationRepository.GetSectionTypes();
        return Ok(result);
    }

    /// <summary>
    /// Статистика операций по скважине, группированная по категориям
    /// </summary>
    /// <param name="idWell">id скважины</param>
    /// <param name="request"></param>      
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpGet("groupStat")]
    [Permission]
    [ProducesResponseType(typeof(IEnumerable<WellGroupOpertionDto>), (int)System.Net.HttpStatusCode.OK)]
    public async Task<IActionResult> GetGroupOperationsAsync(
        [FromRoute] int idWell,
        [FromQuery] WellOperationRequestBase request,
        CancellationToken token)
    {
        if (!await CanUserAccessToWellAsync(idWell, token).ConfigureAwait(false))
            return Forbid();

        var requestToservice = new WellOperationRequest(request, new[] { idWell });

        var result = await wellOperationRepository.GetGroupOperationsStatAsync(requestToservice, token);
        return Ok(result);
    }

    /// <summary>
    /// Возвращает список имен типов операций на скважине
    /// </summary>
    /// <param name="includeParents">флаг, нужно ли включать родителей в список</param>
    /// <returns></returns>
    [HttpGet("categories")]
    [Permission]
    [ProducesResponseType(typeof(IEnumerable<WellOperationCategoryDto>), StatusCodes.Status200OK)]
    public IActionResult GetCategories(bool includeParents = true)
    {
        var result = wellOperationCategoryRepository.Get(includeParents, false);
        return Ok(result);
    }

    /// <summary>
    /// Постраничный список операций на скважине. 
    /// </summary>
    /// <param name="idWell">id скважины</param>
    /// <param name="request"></param>
    /// <param name="token"></param>
    /// <returns>Список операций на скважине</returns>
    [HttpGet]
    [Permission]
    [ProducesResponseType(typeof(PaginationContainer<WellOperationDto>), StatusCodes.Status200OK)]
    public async Task<IActionResult> GetPageOperationsAsync(
        [FromRoute] int idWell,
        [FromQuery] WellOperationRequestBase request,
        CancellationToken token)
    {
        if (!await CanUserAccessToWellAsync(idWell, token))
            return Forbid();

        var requestToService = new WellOperationRequest(request, new[] { idWell });

        var result = await wellOperationRepository.GetPageAsync(requestToService, token);
        return Ok(result);
    }

    /// <summary>
    /// Создает excel файл с "сетевым графиком"
    /// </summary>
    /// <param name="idWell">id скважины</param>
    /// <param name="scheduleReportService"></param>
    /// <param name="token"></param>
    /// <returns>Запрашиваемый файл</returns>
    [HttpGet("scheduleReport")]
    [Permission]
    [ProducesResponseType(typeof(PhysicalFileResult), StatusCodes.Status200OK)]
    public async Task<IActionResult> ScheduleReportAsync([FromRoute] int idWell,
        [FromServices] IScheduleReportService scheduleReportService,
        CancellationToken token)
    {
        var idCompany = User.GetCompanyId();

        if (idCompany is null)
            return Forbid();

        if (!await wellService.IsCompanyInvolvedInWellAsync(idCompany.Value, idWell, token))
            return Forbid();

        var stream = await scheduleReportService.MakeReportAsync(idWell, token);
        var fileName = await wellService.GetWellCaptionByIdAsync(idWell, token) + "_ScheduleReport.xlsx";
        return File(stream, "application/octet-stream", fileName);
    }

    /// <summary>
    /// Удаляет выбранную операцию на скважине
    /// </summary>
    /// <param name="idWell">id скважины</param>
    /// <param name="idOperation">id выбранной операции</param>
    /// <param name="token">Токен отмены задачи</param>
    /// <returns>Количество удаленных из БД строк</returns>
    [HttpDelete("{idOperation}")]
    [Permission]
    [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
    public async Task<IActionResult> DeleteAsync(int idWell, int idOperation, CancellationToken token)
    {
        if (!await CanUserAccessToWellAsync(idWell, token))
            return Forbid();

        if (!await CanUserEditWellOperationsAsync(idWell, token))
            return Forbid();

        var result = await wellOperationRepository.DeleteRangeAsync(new[] { idOperation }, token);

        return Ok(result);
    }

    /// <summary>
    /// Удаляет выбранные операции по скважине
    /// </summary>
    /// <param name="idWell">id скважины</param>
    /// <param name="ids">ids выбранных операций</param>
    /// <param name="token">Токен отмены задачи</param>
    /// <returns>Количество удаленных из БД строк</returns>
    [HttpDelete]
    [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> DeleteRangeAsync([FromRoute] int idWell, IEnumerable<int> ids, CancellationToken token)
    {
        if (!await CanUserAccessToWellAsync(idWell, token))
            return Forbid();

        if (!await CanUserEditWellOperationsAsync(idWell, token))
            return Forbid();

        if (!ids.Any())
            return this.ValidationBadRequest(nameof(ids), "Пустой список операций");

        var result = await wellOperationRepository.DeleteRangeAsync(ids, token);

        if(result == ICrudRepository<WellOperationDto>.ErrorIdNotFound)
            return this.ValidationBadRequest(nameof(ids), "Минимум одна из операций не найдена в базе");

        return Ok(result);
    }

    /// <summary>
    /// Формирование excel файла с операциями на скважине
    /// </summary>
    /// <param name="idWell"></param>
    /// <param name="idType"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpGet("export")]
    [ProducesResponseType(typeof(PhysicalFileResult), StatusCodes.Status200OK, "application/octet-stream")]
    public async Task<IActionResult> ExportAsync(int idWell,
        int idType,
        CancellationToken token)
    {
        var options = new WellOperationExportRequest(idWell, idType);
        var exportService = wellOperationExportServiceFactory.CreateExportService<WellOperationExportRequest>(idType);

        var (fileName, file) = await exportService.ExportAsync(options, token);

        return File(file, "application/octet-stream", fileName);
    }

    /// <summary>
    /// Парсинг ГГД из excel (xlsx) файла
    /// </summary>
    /// <param name="idWell"></param>
    /// <param name="idType"></param>
    /// <param name="file"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpPost("parse/{idType}")]
    [Permission]
    [ProducesResponseType(typeof(ParserResultDto<WellOperationDto>), StatusCodes.Status200OK)]
    public async Task<IActionResult> ParseAsync(int idWell,
        int idType,
        [Required] IFormFile file,
        CancellationToken token)
    {
        if (!await CanUserAccessToWellAsync(idWell, token))
            return Forbid();

        if (!await CanUserEditWellOperationsAsync(idWell, token))
            return Forbid();

        var stream = file.GetExcelFile();

        try
        {
            var timezone = wellService.GetTimezone(idWell);
            var options = new WellOperationParserRequest(idWell, idType, timezone);
            var parser = wellOperationParserFactory.CreateParser<WellOperationParserRequest>(idType);
            var result = parser.Parse(stream, options);

            return Ok(result);
        }
        catch (FileFormatException ex)
        {
            return this.ValidationBadRequest(nameof(file), ex.Message);
        }
    }

    /// <summary>
    /// Получение шаблона для заполнения ГГД
    /// </summary>
    /// <returns></returns>
    [HttpGet("template")]
    [AllowAnonymous]
    [ProducesResponseType(typeof(PhysicalFileResult), StatusCodes.Status200OK, "application/octet-stream")]
    public IActionResult GetTemplate(int idType)
    {
        var parser = wellOperationParserFactory.CreateParser<WellOperationParserRequest>(idType);
        var stream = parser.GetTemplateFile();

        return File(stream, "application/octet-stream", templateNames[idType]);
    }

    private async Task<bool> CanUserAccessToWellAsync(int idWell, CancellationToken token)
    {
        var idCompany = User.GetCompanyId();
        return idCompany is not null && await wellService.IsCompanyInvolvedInWellAsync((int)idCompany,
            idWell, token).ConfigureAwait(false);
    }

    private async Task<bool> CanUserEditWellOperationsAsync(int idWell, CancellationToken token)
    {
        var idUser = User.GetUserId();

        if (!idUser.HasValue)
            return false;

        var well = await wellService.GetOrDefaultAsync(idWell, token);

        if (well is null)
            return false;

        return well.IdState != 2 || userRepository.HasPermission(idUser.Value, "WellOperation.editCompletedWell");
    }
}