Option Explicit On

Imports System.IO
Imports DigitalData.Modules.Interfaces
Imports DigitalData.Modules.Jobs
Imports DigitalData.Modules.Config
Imports DigitalData.Modules.Logging
Imports Newtonsoft.Json.Linq
Imports System.Collections.Generic
Imports System.Linq
Imports System.Text.RegularExpressions
Imports DigitalData.Modules.Database
Imports System.Data

Public Class GraphQLJob
    Inherits JobBase
    Implements IJob(Of GraphQLArgs)

    Private _GraphQL As GraphQLInterface = Nothing

    Private Const PLACEHOLDER_STATIC = "STATIC:"

    Public Sub New(LogConfig As LogConfig, MSSQL As MSSQLServer)
        MyBase.New(LogConfig, Nothing, MSSQL)
    End Sub

    Public Sub Start(Args As GraphQLArgs) Implements IJob(Of GraphQLArgs).Start
        Try
            Dim oConfigPath As String = Args.QueryConfigPath
            Dim oConfigManager As New ConfigManager(Of GraphQLConfig)(_LogConfig, oConfigPath)

            With oConfigManager.Config
                _GraphQL = New GraphQLInterface(_LogConfig, .BaseUrl, .Email, .Password, .CertificateFingerprint)
            End With

            ' Login to get cookie
            _Logger.Debug("Logging in")
            Dim oLoginResponse = _GraphQL.Login()

            ' save cookie for future requests
            _GraphQL.SaveCookies(oLoginResponse.Cookies.Item(0))

            _Logger.Debug("Loading Queries")

            ' Load query data from TBCUST_JOBRUNNER_QUERY
            Dim oQueryTable As DataTable = _MSSQL.GetDatatable("SELECT * FROM TBCUST_JOBRUNNER_QUERY ORDER BY OPERATION_NAME, CLEAR_BEFORE_FILL ASC")
            Dim oQueryList As New List(Of GraphQL.Query)

            ' Save query data to business objects
            For Each oRow As DataRow In oQueryTable.Rows
                Dim oQuery As New GraphQL.Query With {
                    .Id = oRow.Item("GUID"),
                    .Name = oRow.Item("TITLE"),
                    .ClearBeforeFill = oRow.Item("CLEAR_BEFORE_FILL"),
                    .ConnectionId = oRow.Item("CON_ID"), ' TODO: Connection String?
                    .DestinationTable = oRow.Item("DESTINATION_TABLE"),
                    .OperationName = oRow.Item("OPERATION_NAME"),
                    .MappingBasePath = oRow.Item("MAPPING_BASE_PATH"),
                    .QueryString = oRow.Item("QUERY_STRING"),
                    .QueryConstraint = oRow.Item("QUERY_CONSTRAINT")
                }
                oQueryList.Add(oQuery)
            Next

            _Logger.Debug("Getting the data from GraphQL")

            For Each oQuery As GraphQL.Query In oQueryList
                Try
                    _Logger.NewBlock($"Query [{oQuery.Name}]")

                    Dim oConnectionId As Integer = oQuery.ConnectionId
                    Dim oConnectionString = _MSSQL.Get_ConnectionStringforID(oConnectionId)

                    Dim oDatabase As New MSSQLServer(_LogConfig, oConnectionString)

                    ' Reset all records to status = 0
                    _Logger.Info("Resetting data with constraint [{1}]", oQuery.Name, oQuery.QueryConstraint)

                    Dim oResetSQL = $"UPDATE {oQuery.DestinationTable} SET STATUS = 0"
                    If oQuery.QueryConstraint <> String.Empty Then
                        oResetSQL &= $" WHERE {oQuery.QueryConstraint}"
                    End If
                    _MSSQL.ExecuteNonQuery(oResetSQL)

                    _Logger.Info("Getting data..", oQuery.Name)

                    ' get the data from GraphQL
                    Dim oDataResponse = _GraphQL.GetData(oQuery.QueryString, oQuery.OperationName)
                    Dim oResult As String

                    ' write data to string
                    Using oStream = oDataResponse.GetResponseStream()
                        Using oReader As New StreamReader(oStream)
                            oResult = oReader.ReadToEnd()
                        End Using
                    End Using

                    ' Fill the query object with field mapping data from TBCUST_JOBRUNNER_QUERY_MAPPING
                    Dim oSQL As String = "SELECT t2.* FROM TBCUST_JOBRUNNER_QUERY_MAPPING t
                                          JOIN TBCUST_JOBRUNNER_MAPPING t2 ON t.MAPPING_ID = t2.GUID
                                          WHERE t.QUERY_ID = {0}"
                    Dim oMappingTable As DataTable = _MSSQL.GetDatatable(String.Format(oSQL, oQuery.Id))

                    For Each oMapping As DataRow In oMappingTable.Rows
                        oQuery.MappingFields.Add(New GraphQL.FieldMapping With {
                            .DestinationColumn = oMapping.Item("DestinationColumn"),
                            .SourcePath = oMapping.Item("SourcePath")
                        })
                    Next

                    ' Handle the response from GraphQL and insert Data
                    Dim oQueryHandleResult = HandleResponse(oResult, oQuery, oDatabase)

                    If IsNothing(oQueryHandleResult) Then
                        Continue For
                    End If

                    ' Finally delete all old records
                    Dim oDeleteSQL = $"DELETE FROM {oQuery.DestinationTable} WHERE STATUS = 0"
                    If oQuery.QueryConstraint <> String.Empty Then
                        oDeleteSQL &= $" AND {oQuery.QueryConstraint}"
                    End If

                    _Logger.Info("Success, deleting old records..", oQuery.Name)
                    _MSSQL.ExecuteNonQuery(oDeleteSQL)

                Catch ex As Exception
                    _Logger.Warn("Error while getting Data for Name/OperationName [{0}]/[{1}]", oQuery.Name, oQuery.OperationName)
                    _Logger.Error(ex)

                    _Logger.Info("Failure, deleting new records..", oQuery.Name)

                    ' If a crash happens, delete all records which were inserted in this run,
                    ' thus going back to the previous state
                    Dim oDeleteSQL = $"DELETE FROM {oQuery.DestinationTable} WHERE STATUS = 1"
                    If oQuery.QueryConstraint <> String.Empty Then
                        oDeleteSQL &= $" AND {oQuery.QueryConstraint}"
                    End If
                    _MSSQL.ExecuteNonQuery(oDeleteSQL)
                Finally
                    _Logger.EndBlock()
                End Try
            Next

            ' logout
            _Logger.Debug("Logging out")
            Dim oLogoutResponse = _GraphQL.Logout()
        Catch ex As Exception
            _Logger.Error(ex)
            Throw ex
        End Try
    End Sub

    Private Function HandleResponse(JsonString As String, QueryData As GraphQL.Query, DB As Database.MSSQLServer) As GraphQL.Query
        Dim oObj As JObject = JObject.Parse(JsonString)
        Dim oResultList As JToken

        If _GraphQL.ReadJSONPathFragmented(oObj, QueryData.MappingBasePath) = False Then
            _Logger.Warn("There is an error in the MappingBasePath [{1}] configuration of query [{0}]", QueryData.Name, QueryData.MappingBasePath)
        End If

        Try
            oResultList = oObj.SelectToken(QueryData.MappingBasePath, errorWhenNoMatch:=True)
        Catch ex As Exception
            _Logger.Warn("HandleResponse: Could not find BasePath: [{0}] for query [{1}]", QueryData.MappingBasePath, QueryData.Name)
            _Logger.Error(ex)
            Return Nothing
        End Try

        If oResultList Is Nothing Then
            _Logger.Warn("HandleResponse: Could not find BasePath: [{0}] for query [{1}]", QueryData.MappingBasePath, QueryData.Name)
            Return Nothing
        End If

        _Logger.Info("HandleResponse: Processing Queue [{0}] with [{1}] Items", QueryData.Name, oResultList.Count)

        For Each oResultItem As JToken In oResultList
            Try
                Dim oValues As New List(Of String)
                Dim oKeys As New List(Of String)

                For Each oMapping In QueryData.MappingFields
                    Dim oValue As String = String.Empty

                    If oMapping.SourcePath.StartsWith(PLACEHOLDER_STATIC) Then
                        oValue = oMapping.SourcePath.Replace(PLACEHOLDER_STATIC, String.Empty)
                    Else
                        Dim oToken = oResultItem.SelectToken(oMapping.SourcePath)

                        If oToken Is Nothing Then
                            _Logger.Warn("HandleResponse: Could not find value at SourcePath: {0}", oMapping.SourcePath)
                            oValue = String.Empty
                        Else
                            oValue = oToken.ToString
                        End If
                    End If

                    oValues.Add(oValue)
                    oKeys.Add(oMapping.DestinationColumn)
                Next

                Dim oColumnValues = oValues.
                    Select(Function(Value) Regex.Replace(Value, "'", "''")).
                    Select(Function(Value) $"'{Value}'").
                    ToList()
                Dim oValueString = String.Join(",", oColumnValues)

                Dim oColumns = String.Join(",", oKeys.ToArray)
                Dim oSQL As String = $"INSERT INTO {QueryData.DestinationTable} ({oColumns}) VALUES ({oValueString})"

                DB.ExecuteNonQuery(oSQL)
            Catch ex As Exception
                _Logger.Error(ex)
            End Try
        Next

        Return QueryData
    End Function

    Public Function ShouldStart(Arguments As GraphQLArgs) As Boolean Implements IJob(Of GraphQLArgs).ShouldStart
        Return Arguments.Enabled
    End Function
End Class
