Imports System.IO
Imports System.Net
Imports System.Security.Cryptography.X509Certificates
Imports System.Text
Imports DigitalData.Modules.Logging
Imports Newtonsoft.Json

Public Class GraphQLInterface
    Private _logConfig As LogConfig
    Private _logger As Logger
    Private _baseUrl As String
    Private _userEmail As String
    Private _userPassword As String
    Private _certificate As X509Certificate2
    Private _cookieJar As CookieContainer
    Private _Encoding As New UTF8Encoding

    Private Const MAX_COOKIE_SIZE As Integer = 32768
    Private Const MAX_COOKIE_COUNT As Integer = 300
    Private Const MAX_COOKIE_COUNT_PER_DOMAIN As Integer = 20

    Public Property Proxy As WebProxy
    Public Property Credentials As NetworkCredential

    Public Sub New(LogConfig As LogConfig, BaseUrl As String, Email As String, Password As String, CertificateFingerprint As String)
        Try
            _logConfig = LogConfig
            _logger = LogConfig.GetLogger()
            _baseUrl = BaseUrl
            _userEmail = Email
            _userPassword = Password

            Dim oStore As New X509Store(StoreName.Root, StoreLocation.CurrentUser)
            oStore.Open(OpenFlags.ReadOnly)


            _logger.Debug("Available Certificates ({0}):", oStore.Certificates.Count)

            For Each oCert In oStore.Certificates
                _logger.Debug("FriendlyName: {0}", oCert.FriendlyName)
                _logger.Debug("IssuerName: {0}", oCert.IssuerName.Name)
                _logger.Debug("SubjectName: {0}", oCert.SubjectName.Name)
                _logger.Debug("Fingerprint: {0}", oCert.Thumbprint)
            Next

            _logger.Debug("Looking for Certificate with Fingerprint [{0}]", CertificateFingerprint)

            Dim oFoundCerts = oStore.Certificates.Find(X509FindType.FindByThumbprint, CertificateFingerprint, False)

            If oFoundCerts.Count = 0 Then
                _logger.Warn("Certificate could not be found! Exiting.")
                Exit Sub
            End If

            _certificate = oFoundCerts.Item(0)
        Catch ex As Exception
            _logger.Error(ex)
        End Try
    End Sub

    Public Sub SaveCookies(Cookie As Cookie)
        GetCookies().Add(Cookie)
    End Sub

    Public Function Login() As HttpWebResponse
        Try
            Dim oLoginData As New LoginData() With {.email = _userEmail, .token = _userPassword}
            Dim oBytes As Byte() = ToBytes(JsonConvert.SerializeObject(oLoginData))
            Dim oRequest As HttpWebRequest = GetRequest("/login", oBytes)

            Using oStream = oRequest.GetRequestStream()
                oStream.Write(oBytes, 0, oBytes.Length)
            End Using

            Return oRequest.GetResponse()
        Catch ex As Exception
            _logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Function Logout() As HttpWebResponse
        Try
            Dim oLogoutData As New LogoutData() With {.email = _userEmail}
            Dim oBytes As Byte() = ToBytes(JsonConvert.SerializeObject(oLogoutData))
            Dim oRequest As HttpWebRequest = GetRequest("/logout", oBytes)

            Using stream = oRequest.GetRequestStream()
                stream.Write(oBytes, 0, oBytes.Length)
            End Using

            Return oRequest.GetResponse()
        Catch ex As Exception
            _logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Function GetData(Query As String, OperationName As String) As HttpWebResponse
        Try
            Dim oQueryData As New QueryData() With {
                .operationName = OperationName,
                .query = Query,
                .variables = New Object
            }
            Dim oJson = JsonConvert.SerializeObject(oQueryData)
            Dim oBytes = ToBytes(oJson)
            Dim oRequest = GetRequest("/graphql", oBytes)

            Using stream = oRequest.GetRequestStream()
                stream.Write(oBytes, 0, oBytes.Length)
            End Using

            Return oRequest.GetResponse()
        Catch ex As Exception
            _logger.Error(ex)
            Throw ex
        End Try
    End Function

    Public Function ReadJSONPathFragmented(pObject As Linq.JObject, pJsonPath As String)
        Dim oSplitPath As List(Of String) = pJsonPath.Split(".").ToList()
        Dim oCurrentPath As String = String.Empty

        For Each oPart In oSplitPath
            If oCurrentPath = String.Empty Then
                oCurrentPath = oPart
            Else
                oCurrentPath &= "." & oPart
            End If

            _logger.Debug("Selecting Path Fragment [{0}]", oCurrentPath)

            Try
                pObject.SelectToken(oCurrentPath, errorWhenNoMatch:=True)
            Catch ex As Exception
                _logger.Warn("Path Fragment [{0}] did not return a valid token", oCurrentPath)
                Return False
            End Try
        Next

        Return True
    End Function

    Private Function GetRequest(Url As String, PostData As Byte()) As HttpWebRequest
        Try
            Dim oRequest As HttpWebRequest = WebRequest.Create($"{_baseUrl}{Url}")
            oRequest.Method = "POST"
            oRequest.ContentType = "application/json"
            oRequest.ContentLength = PostData.Length
            oRequest.ClientCertificates.Add(_certificate)
            oRequest.CookieContainer = GetCookies()

            oRequest.Proxy = Nothing

            If Proxy Is Nothing Then
                oRequest.Proxy = Nothing
            Else
                oRequest.Proxy = Proxy
                oRequest.Credentials = Credentials
            End If

            Return oRequest
        Catch ex As Exception
            _logger.Error(ex)
            Throw ex
        End Try
    End Function

    Private Function GetCookies() As CookieContainer
        If _cookieJar Is Nothing Then
            _cookieJar = New CookieContainer(MAX_COOKIE_COUNT, MAX_COOKIE_COUNT_PER_DOMAIN, MAX_COOKIE_SIZE)
        End If

        Return _cookieJar
    End Function

    Private Function ToBytes(Str As String) As Byte()
        Return _Encoding.GetBytes(Str)
    End Function
End Class
