using System.Text.Json;
using ApiOPE.Configuration;
using ApiOPE.Contracts;
using ApiOPE.Security;
using ApiOPE.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

namespace ApiOPE.Controllers;

[ApiController]
[Route("")]
public sealed class OpeController : ControllerBase
{
    private static readonly JsonSerializerOptions JsonOptions = new(JsonSerializerDefaults.Web);

    private readonly OpeOptions _options;
    private readonly InternalDenunciasClient _internalApi;
    private readonly OpeFieldMapper _fieldMapper;
    private readonly ILogger<OpeController> _logger;

    public OpeController(
        IOptions<OpeOptions> options,
        InternalDenunciasClient internalApi,
        OpeFieldMapper fieldMapper,
        ILogger<OpeController> logger)
    {
        _options = options.Value;
        _internalApi = internalApi;
        _fieldMapper = fieldMapper;
        _logger = logger;
    }

    [HttpGet("versions/current")]
    [OpeAuthenticated("/versions/current", versionOptional: true)]
    [Produces(OpeMediaTypes.Version)]
    public IActionResult GetCurrentVersion()
        => Json(new OpeVersionResponse(_options.Version), OpeMediaTypes.Version);

    [HttpGet]
    [OpeAuthenticated("/")]
    [Produces(OpeMediaTypes.Bookmarks)]
    public IActionResult GetBookmarks()
    {
        var genericOperationUrl = $"{_options.PublicBaseUrl.TrimEnd('/')}/genericoperations";
        return Json(
            new Dictionary<string, string>(StringComparer.Ordinal)
            {
                ["generic-operation"] = genericOperationUrl
            },
            OpeMediaTypes.Bookmarks);
    }

    [HttpPost("genericoperations")]
    [OpeAuthenticated("/genericoperations")]
    [RequestSizeLimit(64 * 1024)]
    [Produces(OpeMediaTypes.GenericOperationResponse)]
    [ProducesResponseType(typeof(OpeErrorResponse), StatusCodes.Status412PreconditionFailed)]
    public async Task<IActionResult> ExecuteGenericOperation(CancellationToken cancellationToken)
    {
        var requestContext = OpeRequestContextStore.Get(HttpContext)
            ?? throw new InvalidOperationException("No existe contexto de autenticacion OPE.");

        var parsedRequest = await GenericOperationRequestReader.ReadAsync(Request, cancellationToken);
        if (!parsedRequest.IsValid)
        {
            return Error(
                "Existen errores en los campos",
                OpeErrorCauses.FieldErrors,
                parsedRequest.Errors);
        }

        var identifier = parsedRequest.Request!.Data["FIELD_0"].Value;
        if (!DenunciaLookupParser.TryParse(identifier, out var lookup) || lookup is null)
        {
            return Error(
                "Existen errores en los campos",
                OpeErrorCauses.FieldErrors,
                new Dictionary<string, string>(StringComparer.Ordinal)
                {
                    ["FIELD_0"] = OpeFieldErrors.UnexpectedFormat
                });
        }

        try
        {
            var result = await _internalApi.GetFieldsAsync(
                lookup,
                requestContext.TransactionId,
                cancellationToken);

            if (result.Status == InternalLookupStatus.NotFound || result.Fields is null)
            {
                return Error(
                    "No se ha encontrado la denuncia o expediente solicitado",
                    OpeErrorCauses.ElementNotExists,
                    null);
            }

            return Json(_fieldMapper.Map(result.Fields), OpeMediaTypes.GenericOperationResponse);
        }
        catch (OperationCanceledException) when (!HttpContext.RequestAborted.IsCancellationRequested)
        {
            _logger.LogWarning(
                "Timeout consultando ApiDenuncias para una OPE. TransactionId={TransactionId}",
                requestContext.TransactionId);
            return Error("Error en el conector", OpeErrorCauses.ConnectorError, null);
        }
        catch (HttpRequestException exception)
        {
            _logger.LogWarning(
                exception,
                "No se ha podido conectar con ApiDenuncias. TransactionId={TransactionId}",
                requestContext.TransactionId);
            return Error("Error en el conector", OpeErrorCauses.ConnectorError, null);
        }
        catch (InternalApiException exception)
        {
            _logger.LogWarning(
                exception,
                "ApiDenuncias ha devuelto una respuesta no utilizable. TransactionId={TransactionId}",
                requestContext.TransactionId);
            return Error("Error en el conector", OpeErrorCauses.ConnectorError, null);
        }
        catch (InvalidDataException exception)
        {
            _logger.LogError(
                exception,
                "El contrato entre ApiOPE y ApiDenuncias no coincide. TransactionId={TransactionId}",
                requestContext.TransactionId);
            return Error("Error en el conector", OpeErrorCauses.ConnectorError, null);
        }
    }

    private static ContentResult Error(
        string message,
        string cause,
        IReadOnlyDictionary<string, string>? data)
    {
        var response = new OpeErrorResponse(
            StatusCodes.Status412PreconditionFailed,
            message,
            new OpeTypedInfo(null, null, null, cause),
            data);

        return Json(
            response,
            OpeMediaTypes.Error,
            StatusCodes.Status412PreconditionFailed);
    }

    private static ContentResult Json<T>(
        T value,
        string contentType,
        int statusCode = StatusCodes.Status200OK)
    {
        return new ContentResult
        {
            Content = JsonSerializer.Serialize(value, JsonOptions),
            ContentType = contentType,
            StatusCode = statusCode
        };
    }
}
