Imports System.Net
Imports System.Net.Http
Imports System.Text
Imports System.Threading.Tasks
Imports System.Web
Imports System.Web.Caching
Imports Newtonsoft.Json

Public Class LoginCertBridge
    Inherits System.Web.UI.Page

    Protected Sub Page_Load(sender As Object, e As EventArgs) Handles Me.Load
        RegisterAsyncTask(New PageAsyncTask(AddressOf Procesar))
    End Sub

    Private Async Function Procesar() As Threading.Tasks.Task
        Dim iframe As Boolean = String.Equals(Convert.ToString(Request.QueryString("iframe")), "true", StringComparison.OrdinalIgnoreCase)
        Dim parentOrigin As String = Convert.ToString(Request.QueryString("parentOrigin"))
        Dim returnUrl As String = Convert.ToString(Request.QueryString("returnUrl"))
        Dim state As String = Convert.ToString(Request.QueryString("state"))
        Dim options = Models.CertificateLoginBridgeOptions.LoadFromConfiguration()

        Try
            If Not Models.CertificateLoginBridgeHelper.IsCertificateHostRequest(Request, options.PublicUrl) Then
                ResponderError("Not Found", 404, iframe, parentOrigin)
                Return
            End If

            If iframe AndAlso Not Models.CertificateLoginBridgeHelper.IsAllowedParentOrigin(parentOrigin, options.AllowedParentOrigins) Then
                ResponderError("El origen padre no está permitido.", 400, iframe, parentOrigin)
                Return
            End If

            Dim cert = Models.CertificateLoginBridgeHelper.ReadCertificate(
                Request,
                options.ForwardedCertificateHeader,
                options.AdditionalForwardedCertificateHeaders)

            If cert Is Nothing Then
                ResponderError("Certificado no proporcionado.", 401, iframe, parentOrigin)
                Return
            End If

            Dim dni As String = Models.CertificateLoginBridgeHelper.ObtenerDni(cert)
            If String.IsNullOrWhiteSpace(dni) Then
                ResponderError("No se pudo obtener un DNI válido del certificado.", 401, iframe, parentOrigin)
                Return
            End If

            If Not String.IsNullOrWhiteSpace(returnUrl) Then
                If Not Models.CertificateLoginBridgeHelper.IsAllowedParentOrigin(returnUrl, options.AllowedParentOrigins) Then
                    ResponderError("La URL de retorno no esta permitida.", 400, iframe, parentOrigin)
                    Return
                End If

                If String.IsNullOrWhiteSpace(state) Then
                    ResponderError("No se recibio el estado del acceso con certificado.", 400, iframe, parentOrigin)
                    Return
                End If

                HttpRuntime.Cache.Insert(
                    GetCacheKey(state),
                    dni,
                    Nothing,
                    DateTime.UtcNow.AddMinutes(2),
                    Cache.NoSlidingExpiration)

                Response.Redirect(BuildReturnUrl(returnUrl, state), False)
                Context.ApplicationInstance.CompleteRequest()
                Return
            End If

            If iframe Then
                Me.Response.StatusCode = 200
                Me.Response.TrySkipIisCustomErrors = True
                Me.Response.ContentType = "text/html; charset=utf-8"
                Me.Response.Write(Models.CertificateLoginBridgeHelper.BuildIframeDniHtml(dni, parentOrigin))
                Me.Response.Flush()
                Context.ApplicationInstance.CompleteRequest()
                Return
            End If

            Dim endpoint As String = ConfigurationManager.AppSettings("CertLoginEndpoint")
            If String.IsNullOrWhiteSpace(endpoint) Then
                endpoint = "api/Auth/login-cert-proxy"
            End If

            Dim payload As String = "{""Dni"":""" & EscapeJson(dni) & """}"
            Dim swaggerResult = Await EjecutarLoginCertificadoAsync(endpoint, payload)
            Dim responseContent = swaggerResult.ResponseContent

            If Not swaggerResult.IsSuccessStatusCode Then
                ResponderError(
                    "Swagger devolvió " & CInt(swaggerResult.StatusCode).ToString() & ": " & responseContent,
                    CInt(swaggerResult.StatusCode),
                    iframe,
                    parentOrigin)
                Return
            End If

            Dim loginResponse = JsonConvert.DeserializeObject(Of Models.CertificateProxyLoginResponse)(responseContent)
            If loginResponse Is Nothing OrElse
                String.IsNullOrWhiteSpace(loginResponse.Token) OrElse
                loginResponse.User Is Nothing Then
                ResponderError("Respuesta inválida del servicio de autenticación.", 502, iframe, parentOrigin)
                Return
            End If

            Me.Response.StatusCode = 200
            Me.Response.TrySkipIisCustomErrors = True

            If iframe Then
                Me.Response.ContentType = "text/html; charset=utf-8"
                Me.Response.Write(Models.CertificateLoginBridgeHelper.BuildIframeLoginHtml(loginResponse, parentOrigin))
            Else
                Me.Response.ContentType = "application/json; charset=utf-8"
                Me.Response.Write(responseContent)
            End If

            Me.Response.Flush()
            Context.ApplicationInstance.CompleteRequest()
        Catch ex As Exception
            ResponderError("Error interno: " & ex.Message, 500, iframe, parentOrigin)
        End Try
    End Function

    Private Sub ResponderError(mensaje As String, statusCode As Integer, iframe As Boolean, parentOrigin As String)
        Me.Response.TrySkipIisCustomErrors = True

        If iframe Then
            Me.Response.StatusCode = 200
            Me.Response.Headers("X-Cert-Bridge-StatusCode") = statusCode.ToString()
            Me.Response.ContentType = "text/html; charset=utf-8"
            Me.Response.Write(Models.CertificateLoginBridgeHelper.BuildIframeErrorHtml(mensaje, parentOrigin))
            Me.Response.Flush()
            Context.ApplicationInstance.CompleteRequest()
            Return
        End If

        Me.Response.StatusCode = statusCode
        Me.Response.ContentType = "text/plain; charset=utf-8"
        Me.Response.Write(mensaje)
        Me.Response.Flush()
        Context.ApplicationInstance.CompleteRequest()
    End Sub

    Private Async Function EjecutarLoginCertificadoAsync(endpoint As String, payload As String) As Task(Of SwaggerProxyResult)
        Dim lastException As Exception = Nothing

        For Each baseAddress In ObtenerSwaggerBaseAddresses()
            Try
                Dim client = HttpClientFactory.Create()
                client.BaseAddress = baseAddress
                client.Timeout = TimeSpan.FromSeconds(20)

                Using content As New StringContent(payload, Encoding.UTF8, "application/json")
                    Dim httpResponse = Await client.PostAsync(endpoint, content).ConfigureAwait(False)
                    Dim responseContent = Await httpResponse.Content.ReadAsStringAsync().ConfigureAwait(False)

                    Return New SwaggerProxyResult With {
                        .StatusCode = httpResponse.StatusCode,
                        .ResponseContent = responseContent
                    }
                End Using
            Catch ex As Exception When TypeOf ex Is HttpRequestException OrElse TypeOf ex Is TaskCanceledException OrElse TypeOf ex Is InvalidOperationException
                lastException = ex
            End Try
        Next

        Throw New Exception(
            "No se pudo conectar con el servicio de autenticación configurado (" & UtilAntifraude.urlSwagger() & ").",
            lastException)
    End Function

    Private Iterator Function ObtenerSwaggerBaseAddresses() As IEnumerable(Of Uri)
        Dim seen As New HashSet(Of String)(StringComparer.OrdinalIgnoreCase)

        For Each candidate In ObtenerSwaggerBaseAddressCandidates()
            Dim normalizedCandidate = candidate.Trim()
            If normalizedCandidate = "" Then
                Continue For
            End If

            Dim baseUri As Uri = Nothing
            If Not Uri.TryCreate(normalizedCandidate, UriKind.Absolute, baseUri) Then
                Continue For
            End If

            Dim key = baseUri.AbsoluteUri.TrimEnd("/"c)
            If seen.Add(key) Then
                Yield baseUri
            End If
        Next
    End Function

    Private Iterator Function ObtenerSwaggerBaseAddressCandidates() As IEnumerable(Of String)
        Dim configuredUrl = UtilAntifraude.urlSwagger()
        If Not String.IsNullOrWhiteSpace(configuredUrl) Then
            Yield configuredUrl

            Dim configuredUri As Uri = Nothing
            If Uri.TryCreate(configuredUrl, UriKind.Absolute, configuredUri) AndAlso
                String.Equals(configuredUri.Scheme, Uri.UriSchemeHttps, StringComparison.OrdinalIgnoreCase) AndAlso
                EsHostLocal(configuredUri.Host) Then

                Dim httpBuilder As New UriBuilder(configuredUri) With {
                    .Scheme = Uri.UriSchemeHttp,
                    .Port = configuredUri.Port
                }

                Yield httpBuilder.Uri.AbsoluteUri
            End If
        End If

        Dim explicitFallback = ConfigurationManager.AppSettings("SwaggerVBFallback")
        If Not String.IsNullOrWhiteSpace(explicitFallback) Then
            Yield explicitFallback
        End If
    End Function

    Private Function EsHostLocal(host As String) As Boolean
        Return String.Equals(host, "localhost", StringComparison.OrdinalIgnoreCase) OrElse
            String.Equals(host, "127.0.0.1", StringComparison.OrdinalIgnoreCase) OrElse
            String.Equals(host, "::1", StringComparison.OrdinalIgnoreCase)
    End Function

    Private Function EscapeJson(valor As String) As String
        If valor Is Nothing Then Return ""
        Return valor.Replace("\", "\\").Replace("""", "\""").Replace(vbCrLf, "\n").Replace(vbLf, "\n").Replace(vbCr, "\n")
    End Function

    Private Function BuildReturnUrl(returnUrl As String, state As String) As String
        Dim builder As New UriBuilder(returnUrl)
        Dim query = HttpUtility.ParseQueryString(builder.Query)
        query("state") = state.Trim()
        builder.Query = query.ToString()
        Return builder.Uri.AbsoluteUri
    End Function

    Private Function GetCacheKey(state As String) As String
        Return "CertLoginState:" & state.Trim()
    End Function

    Private NotInheritable Class SwaggerProxyResult
        Public Property StatusCode As HttpStatusCode
        Public Property ResponseContent As String = String.Empty

        Public ReadOnly Property IsSuccessStatusCode As Boolean
            Get
                Return CInt(StatusCode) >= 200 AndAlso CInt(StatusCode) <= 299
            End Get
        End Property
    End Class
End Class
