using AsbCloudApp.Data;
using AsbCloudApp.Data.ProcessMaps;
using AsbCloudApp.Exceptions;
using AsbCloudApp.Repositories;
using AsbCloudApp.Requests;
using AsbCloudApp.Requests.ExportOptions;
using AsbCloudApp.Requests.ParserOptions;
using AsbCloudApp.Services;
using AsbCloudApp.Services.Export;
using AsbCloudApp.Services.Parsers;
using AsbCloudDb.Model.ProcessMapPlan;
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.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace AsbCloudWebApi.Controllers.ProcessMaps;

/// <summary>
/// РТК план базовый
/// </summary>
[ApiController]
[Route("api/well/{idWell}/[controller]")]
[Authorize]
public abstract class ProcessMapPlanBaseController<TEntity, TDto> : ControllerBase
    where TEntity : ProcessMapPlanBase
    where TDto : ProcessMapPlanBaseDto
{
    private readonly IChangeLogRepository<TDto, ProcessMapPlanBaseRequestWithWell> repository;
    private readonly IWellService wellService;
    private readonly IParserService<TDto, WellRelatedParserRequest> parserService;
    private readonly ITelemetryService telemetryService;
    private readonly IExportService<WellRelatedExportRequest> processMapPlanExportService;

    protected ProcessMapPlanBaseController(IChangeLogRepository<TDto, ProcessMapPlanBaseRequestWithWell> repository,
        IWellService wellService,
        IParserService<TDto, WellRelatedParserRequest> parserService,
        IExportService<WellRelatedExportRequest> processMapPlanExportService,
        ITelemetryService telemetryService)
    {
        this.repository = repository;
        this.wellService = wellService;
        this.parserService = parserService;
        this.telemetryService = telemetryService;
        this.processMapPlanExportService = processMapPlanExportService;
    }

    protected abstract string TemplateFileName { get; }

    /// <summary>
    /// Добавление
    /// </summary>
    /// <param name="idWell"></param>
    /// <param name="dtos"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpPost]
    [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> InsertRange([FromRoute][Range(0, int.MaxValue)] int idWell, IEnumerable<TDto> dtos, CancellationToken token)
    {
        var idUser = await AssertUserHasAccessToWell(idWell, token);

        foreach (var dto in dtos)
            dto.IdWell = idWell;

        var result = await repository.InsertRange(idUser, dtos, token);
        return Ok(result);
    }

    /// <summary>
    /// Удалить все по скважине и добавить новые
    /// </summary>
    /// <param name="idWell"></param>
    /// <param name="dtos"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpPost("replace")]
    [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> ClearAndInsertRange([FromRoute] int idWell, IEnumerable<TDto> dtos, CancellationToken token)
    {
        var idUser = await AssertUserHasAccessToWell(idWell, token);

        foreach (var dto in dtos)
            dto.IdWell = idWell;

        var request = new ProcessMapPlanBaseRequestWithWell(idWell);
        var result = await repository.ClearAndInsertRange(idUser, request, dtos, token);
        return Ok(result);
    }

    /// <summary>
    /// Пометить записи как удаленные
    /// </summary>
    /// <param name="idWell"></param>
    /// <param name="ids"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpDelete]
    [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> MarkAsDeleted([FromRoute] int idWell, IEnumerable<int> ids, CancellationToken token)
    {
        var idUser = await AssertUserHasAccessToWell(idWell, token);

        var result = await repository.MarkAsDeleted(idUser, ids, token);
        return Ok(result);
    }

    /// <summary>
    /// Очистка
    /// </summary>
    /// <param name="idWell"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpDelete("clear")]
    [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> Clear([FromRoute] int idWell, CancellationToken token)
    {
        var idUser = await AssertUserHasAccessToWell(idWell, token);

        var request = new ProcessMapPlanBaseRequestWithWell(idWell);
        var result = await repository.Clear(idUser, request, token);
        return Ok(result);
    }

    /// <summary>
    /// Получение текущих записей по параметрам
    /// </summary>
    /// <param name="idWell"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpGet]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<IEnumerable<TDto>>> GetCurrent([FromRoute] int idWell, CancellationToken token)
    {
        await AssertUserHasAccessToWell(idWell, token);

        var serviceRequest = new ProcessMapPlanBaseRequestWithWell(idWell);

        var result = await repository.GetCurrent(serviceRequest, token);
        return Ok(result);
    }

    /// <summary>
    /// Получение измененных записей за определенную дату по ключу скважины
    /// </summary>
    /// <param name="idWell"></param>
    /// <param name="moment"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpGet("changelogByMoment")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<IEnumerable<ChangeLogDto<TDto>>>> GetChangeLogByMoment([FromRoute] int idWell, DateTimeOffset? moment, CancellationToken token)
    {
        await AssertUserHasAccessToWell(idWell, token);

        var dataRequest = new ProcessMapPlanBaseRequestWithWell(idWell);

        var changeLogRequest = new ChangeLogRequest()
        {
            Moment = moment
        };

        var builder = repository
            .GetQueryBuilder(changeLogRequest)
            .ApplyRequest(dataRequest);

        var dtos = await builder.GetChangeLogData(token);

        return Ok(dtos);
    }

    /// <summary>
    /// Получение записей за определенную дату по uid
    /// </summary>
    /// <param name="uid"></param>
    /// <param name="updateFrom"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    [AllowAnonymous]
    [HttpGet("/api/telemetry/{uid}/[controller]")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<IEnumerable<ChangeLogDto<TDto>>>> GetByUid(string uid, DateTimeOffset? updateFrom, CancellationToken token)
    {
        var idWell = telemetryService.GetIdWellByTelemetryUid(uid) ?? -1;

        if (idWell < 0)
            return this.ValidationBadRequest(nameof(uid), "Скважина по uid не найдена");

        var serviceRequest = new ProcessMapPlanBaseRequestWithWell(idWell)
        {
            UpdateFrom = updateFrom,
        };

        var result = await repository.GetChangeLogForDate(serviceRequest, null, token);
        return Ok(result);
    }

    /// <summary>
    /// Изменения за определенную дату
    /// </summary>
    /// <param name="idWell"></param>
    /// <param name="date"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpGet("changeLogForDate")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<IEnumerable<ChangeLogDto<TDto>>>> GetChangeLogForDate([FromRoute] int idWell, [FromQuery] DateOnly? date, CancellationToken token)
    {
        await AssertUserHasAccessToWell(idWell, token);

        var serviceRequest = new ProcessMapPlanBaseRequestWithWell(idWell);
        var result = await repository.GetChangeLogForDate(serviceRequest, date, token);
        return Ok(result);
    }

    /// <summary>
    /// Даты за которые есть изменения
    /// </summary>
    /// <param name="idWell"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpGet("dates")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<IEnumerable<DateOnly>>> GetDatesChange([FromRoute] int idWell, CancellationToken token)
    {
        await AssertUserHasAccessToWell(idWell, token);

        var serviceRequest = new ProcessMapPlanBaseRequestWithWell(idWell);
        var result = await repository.GetDatesChange(serviceRequest, token);
        return Ok(result);
    }

    /// <summary>
    /// Редактирование или добавление [для пакетного редактирования]
    /// </summary>
    /// <param name="idWell"></param>
    /// <param name="dtos"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpPut()]
    [ProducesResponseType(typeof(int), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<IActionResult> UpdateOrInsertRange([FromRoute] int idWell, IEnumerable<TDto> dtos, CancellationToken token)
    {
        if (!dtos.Any())
            return NoContent();

        var idUser = await AssertUserHasAccessToWell(idWell, token);

        foreach (var dto in dtos)
            dto.IdWell = idWell;

        var result = await repository.UpdateOrInsertRange(idUser, dtos, token);
        return Ok(result);
    }

    /// <summary>
    /// Импорт РТК из excel (xlsx) файла
    /// </summary>
    /// <param name="idWell"></param>
    /// <param name="file"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    [HttpPost("parse")]
    [ProducesResponseType(StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)]
    public async Task<ActionResult<ParserResultDto<TDto>>> Parse(int idWell,
         [Required] IFormFile file,
        CancellationToken token)
    {
        await AssertUserHasAccessToWell(idWell, token);

        var stream = file.GetExcelFile();

        try
        {
            var options = new WellRelatedParserRequest(idWell);
            var dto = parserService.Parse(stream, options);

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

    /// <summary>
    /// Получение шаблона для заполнения РТК
    /// </summary>
    /// <returns></returns>
    [HttpGet("template")]
    [AllowAnonymous]
    [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK, "application/octet-stream")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    public IActionResult GetTemplate()
    {
        var stream = parserService.GetTemplateFile();
        return File(stream, "application/octet-stream", TemplateFileName);
    }

    /// <summary>
    /// returns user id, if he has access to well
    /// </summary>
    /// <param name="idWell"></param>
    /// <param name="token"></param>
    /// <returns></returns>
    /// <exception cref="ForbidException"></exception>
    private async Task<int> AssertUserHasAccessToWell(int idWell, CancellationToken token)
    {
        var idUser = GetUserId();
        var idCompany = User.GetCompanyId();

        if (!idCompany.HasValue)
            throw new ForbidException("Нет доступа к скважине");

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

    /// <summary>
    /// Формируем excel файл с текущими строками РТК
    /// </summary>
    /// <param name="idWell">id скважины</param>
    /// <param name="token"></param>
    /// <returns>Запрашиваемый файл</returns>
    [HttpGet("export")]
    [ProducesResponseType(typeof(PhysicalFileResult), (int)System.Net.HttpStatusCode.OK, "application/octet-stream")]
    [ProducesResponseType(StatusCodes.Status204NoContent)]
    public async Task<IActionResult> ExportAsync([FromRoute] int idWell, CancellationToken token)
    {
        var exportOptions = new WellRelatedExportRequest(idWell);
        var (fileName, file) = await processMapPlanExportService.ExportAsync(exportOptions, token);
        return File(file, "application/octet-stream", fileName);
    }

    /// <summary>
    /// returns user id or throw
    /// </summary>
    /// <returns></returns>
    /// <exception cref="ForbidException"></exception>
    private int GetUserId()
    {
        var idUser = User.GetUserId() ?? throw new ForbidException("Неизвестный пользователь");
        return idUser;
    }
}