Imports System.IO
Imports System.ServiceModel
Imports System.ServiceModel.Description
Imports DigitalData.Modules.Database
Imports DigitalData.Modules.Logging
Imports DigitalData.Modules.Language
Imports DigitalData.Modules
Imports DigitalData.Modules.EDMI
Imports DigitalData.Modules.EDMI.API.Rights
Imports DigitalData.Services.EDMIService.Messages
Imports DigitalData.Services.EDMIService.Exceptions
Imports DigitalData.Services.EDMIService.Methods
Imports DigitalData.Services.EDMIService.Methods.SetAttributeValue
Imports DigitalData.Services.EDMIService.Methods.Database
Imports DigitalData.Services.EDMIService.Methods.IDB

<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession)>
Public Class EDMIService
    Implements IEDMIService

    Public Shared LogConfig As LogConfig
    Public Shared MSSQL_ECM As MSSQLServer
    Public Shared MSSQL_IDB As MSSQLServer
    Public Shared Firebird As Firebird
    Public Shared AppConfig As Config
    Public Shared Filesystem As Filesystem.File
    Public Shared EDMIArchive As EDMI.File.Archive
    Public Shared GlobalState As GlobalState
    Public Shared Scheduler As Scheduler

    Public Const TBIDB_DOC_INFO = "TBIDB_DOC_INFO"
    Public Const TBIDB_ACCESSRIGHT = "TBIDB_ACCESSRIGHT"

    Private ReadOnly _Logger As Logger
    Private ReadOnly _Debug As Boolean = False
    Private ReadOnly _Username As String
    Private ReadOnly _IDBHelpers As IDB.Helpers


    Public Shared Sub Configure(Config As ServiceConfiguration)
        Dim oBaseAddress = Config.BaseAddresses.Item(0)
        Dim oBinding = API.Channel.GetBinding()
        Dim oAddress = New EndpointAddress(oBaseAddress)
        ' See: https://stackoverflow.com/questions/42327988/addserviceendpoint-throws-key-is-null
        Dim oDescription = ContractDescription.GetContract(GetType(IEDMIService), GetType(EDMIService))
        Dim oEndpoint As New ServiceEndpoint(oDescription, oBinding, oAddress)

        Config.AddServiceEndpoint(oEndpoint)
        Config.Description.Behaviors.Add(New ServiceDebugBehavior With {.IncludeExceptionDetailInFaults = True})
    End Sub

    Public Sub New()
        Dim oOperationContext As OperationContext = OperationContext.Current
        Dim oInstanceContext As InstanceContext = oOperationContext.InstanceContext
        Dim oUsername = StripDomainFromUsername(oOperationContext.ServiceSecurityContext.WindowsIdentity.Name)

        _Username = oUsername

        _Logger = LogConfig.GetLogger()
        _Logger.Debug("New Request by User [{0}]", _Username)

        _IDBHelpers = New IDB.Helpers(LogConfig, MSSQL_IDB)
    End Sub

#Region "=== Base ==="
    Public Function Heartbeat() As Boolean Implements IEDMIService.Heartbeat
        Return True
    End Function

    Public Function GetClientConfig() As Base.GetClientConfig.GetClientConfigResponse Implements IEDMIService.GetClientConfig
        _Logger.Debug("Start of Method [GetClientConfig]")
        Dim oGetClientConfig As New Base.GetClientConfig.GetClientConfigMethod(LogConfig, MSSQL_IDB, MSSQL_ECM, GlobalState)
        Return oGetClientConfig.Run()
    End Function
#End Region

    Public Function NewFile(Data As NewFile.NewFileRequest) As NewFile.NewFileResponse Implements IEDMIService.NewFile
        _Logger.Debug("Start of Method [NewFile]")
        Dim oNewFile As New NewFile.NewFileMethod(LogConfig, MSSQL_IDB, MSSQL_ECM, GlobalState)
        Return oNewFile.Run(Data)
    End Function

    Public Function UpdateFile(Data As UpdateFile.UpdateFileRequest) As UpdateFile.UpdateFileResponse Implements IEDMIService.UpdateFile
        _Logger.Debug("Start of Method [VersionFile]")
        Dim oVersionFile As New UpdateFile.UpdateFileMethod(LogConfig, MSSQL_IDB, MSSQL_ECM, GlobalState)
        Return oVersionFile.Run(Data)
    End Function

    Public Function SetAttributeValue(Data As SetAttributeValue.SetAttributeValueRequest) As SetAttributeValue.SetAttributeValueResponse Implements IEDMIService.SetAttributeValue
        _Logger.Debug("Start of Method [SetAttributeValue]")
        Dim oSetAttributeValue As New SetAttributeValue.SetAttributeValueMethod(LogConfig, MSSQL_IDB, MSSQL_ECM, GlobalState)
        Return oSetAttributeValue.Run(Data)
    End Function

    Public Function ImportFile(pData As GlobalIndexer.ImportFile.ImportFileRequest) As GlobalIndexer.ImportFile.ImportFileResponse Implements IEDMIService.ImportFile
        _Logger.Debug("Start of Method [ImportFile]")
        Dim oImportFile As New GlobalIndexer.ImportFile.ImportFileMethod(LogConfig, MSSQL_IDB, MSSQL_ECM, GlobalState)
        Return oImportFile.Run(pData)
    End Function

    Public Function GetFileObject(pData As GetFileObject.GetFileObjectRequest) As GetFileObject.GetFileObjectResponse Implements IEDMIService.GetFileObject
        _Logger.Debug("Start of Method [GetFileObject]")
        Dim oGetFileObject As New GetFileObject.GetFileObjectMethod(LogConfig, MSSQL_IDB, MSSQL_ECM, GlobalState)
        Return oGetFileObject.Run(pData)
    End Function


#Region "=== Database ==="
    Public Function GetCachedTables() As List(Of String) Implements IEDMIService.GetCachedTables
        Try
            Return GlobalState.GetCachedTables()
        Catch ex As Exception
            _Logger.Error(ex)
            Return New List(Of String)
        End Try
    End Function

    Public Function ReturnDatatableFromCache(Name As String, FilterExpression As String, SortByColumn As String) As TableResult Implements IEDMIService.ReturnDatatableFromCache
        Dim oReturnDatatableFromCache As New GetDatatableFromCache.GetDatatableFromCacheMethod(LogConfig, MSSQL_IDB, MSSQL_ECM, GlobalState)
        Dim oResult = oReturnDatatableFromCache.Run(New GetDatatableFromCache.GetDatatableFromCacheRequest With {
            .DataTable = Name,
            .FilterExpression = FilterExpression,
            .SortByColumn = SortByColumn
        })

        If oResult.OK Then
            Return New TableResult(oResult.Table)
        Else
            Return New TableResult(oResult.ErrorMessage)
        End If
    End Function

    Public Function ReturnDatatable(pData As GetDatatable.GetDatatableRequest) As GetDatatable.GetDatatableResponse Implements IEDMIService.ReturnDatatable
        _Logger.Debug("Start of Method [ReturnDatatable]")
        Dim oGetDatatable As New GetDatatable.GetDatatableMethod(LogConfig, MSSQL_IDB, MSSQL_ECM, GlobalState)
        Return oGetDatatable.Run(pData)
    End Function

    Public Function ReturnScalarValue(pData As GetScalarValue.GetScalarValueRequest) As GetScalarValue.GetScalarValueResponse Implements IEDMIService.ReturnScalarValue
        _Logger.Debug("Start of Method [ReturnScalarValue]")
        Dim oGetScalarValue As New GetScalarValue.GetScalarValueMethod(LogConfig, MSSQL_IDB, MSSQL_ECM, GlobalState)
        Return oGetScalarValue.Run(pData)
    End Function

    Public Function ExecuteNonQuery(pData As ExecuteNonQuery.ExecuteNonQueryRequest) As ExecuteNonQuery.ExecuteNonQueryResponse Implements IEDMIService.ExecuteNonQuery
        _Logger.Debug("Start of Method [ExecuteNonQuery]")
        Dim oExecuteNonQuery As New ExecuteNonQuery.ExecuteNonQueryMethod(LogConfig, MSSQL_IDB, MSSQL_ECM, GlobalState)
        Return oExecuteNonQuery.Run(pData)
    End Function
#End Region

#Region "=== Database (MSSQL IDB) ==="
    Public Function ReturnDatatable_MSSQL_IDB(SQL As String) As TableResult Implements IEDMIService.ReturnDatatable_MSSQL_IDB
        Try
            _Logger.Info($"ReturnDatatable_MSSQL_IDB, SQL: {SQL}")
            Dim oResult As DataTable = MSSQL_IDB.GetDatatable(SQL)
            oResult.TableName = "DD_RESULT"
            Return New TableResult(oResult)

        Catch ex As FaultException
            _Logger.Error(ex)
            Throw ex

        Catch ex As Exception
            _Logger.Error(ex)
            Throw GetFault(ex)

        End Try
    End Function

    Public Function ReturnScalar_MSSQL_IDB(SQL As String) As ScalarResult Implements IEDMIService.ReturnScalar_MSSQL_IDB
        Try
            _Logger.Info($"ReturnScalar_MSSQL_IDB, SQL: {SQL}")
            Dim oResult As Object = MSSQL_IDB.GetScalarValue(SQL)
            Return New ScalarResult(oResult)

        Catch ex As FaultException
            _Logger.Error(ex)
            Throw ex

        Catch ex As Exception
            _Logger.Error(ex)
            Throw GetFault(ex)

        End Try
    End Function

    Public Function ExecuteNonQuery_MSSQL_IDB(SQL As String) As NonQueryResult Implements IEDMIService.ExecuteNonQuery_MSSQL_IDB
        Try
            _Logger.Info($"ExecuteNonQuery_MSSQL_IDB, SQL: {SQL}")
            Dim oResult As Boolean = MSSQL_IDB.ExecuteNonQuery(SQL)
            Return New NonQueryResult()

        Catch ex As FaultException
            _Logger.Error(ex)
            Throw ex

        Catch ex As Exception
            _Logger.Error(ex)
            Throw GetFault(ex)

        End Try
    End Function
#End Region

#Region "=== Database (MSSQL ECM) ==="
    Public Function ReturnDatatable_MSSQL_ECM(SQL As String) As TableResult Implements IEDMIService.ReturnDatatable_MSSQL_ECM
        Try
            _Logger.Info($"ReturnDatatable_MSSQL_ECM, SQL: {SQL}")
            Dim oResult As DataTable = MSSQL_ECM.GetDatatable(SQL)
            oResult.TableName = "DD_RESULT"
            Return New TableResult(oResult)

        Catch ex As FaultException
            _Logger.Error(ex)
            Throw ex

        Catch ex As Exception
            _Logger.Error(ex)
            Throw GetFault(ex)

        End Try
    End Function

    Public Function ReturnScalar_MSSQL_ECM(SQL As String) As ScalarResult Implements IEDMIService.ReturnScalar_MSSQL_ECM
        Try
            _Logger.Info($"ReturnScalar_MSSQL_ECM, SQL: {SQL}")
            Dim oResult As Object = MSSQL_ECM.GetScalarValue(SQL)
            Return New ScalarResult(oResult)

        Catch ex As FaultException
            _Logger.Error(ex)
            Throw ex

        Catch ex As Exception
            _Logger.Error(ex)
            Throw GetFault(ex)

        End Try
    End Function

    Public Function ExecuteNonQuery_MSSQL_ECM(SQL As String) As NonQueryResult Implements IEDMIService.ExecuteNonQuery_MSSQL_ECM
        Try
            _Logger.Info($"ExecuteNonQuery_MSSQL_ECM, SQL: {SQL}")
            Dim oResult As Boolean = MSSQL_ECM.ExecuteNonQuery(SQL)
            Return New NonQueryResult()

        Catch ex As FaultException
            _Logger.Error(ex)
            Throw ex

        Catch ex As Exception
            _Logger.Error(ex)
            Throw GetFault(ex)

        End Try
    End Function
#End Region

#Region "=== Database (Firebird) ==="
    Public Function ReturnDatatable_Firebird(SQL As String) As TableResult Implements IEDMIService.ReturnDatatable_Firebird
        Try
            _Logger.Info($"ReturnDatatable, SQL: {SQL}")
            Dim oResult As DataTable = Firebird.GetDatatable(SQL)
            oResult.TableName = "DD_RESULT"
            Return New TableResult(oResult)

        Catch ex As FaultException
            _Logger.Error(ex)
            Throw ex

        Catch ex As Exception
            _Logger.Error(ex)
            Throw GetFault(ex)

        End Try
    End Function

    Public Function ReturnScalar_Firebird(SQL As String) As ScalarResult Implements IEDMIService.ReturnScalar_Firebird
        Try
            _Logger.Info($"ReturnScalar, SQL: {SQL}")
            Dim oResult As Object = Firebird.GetScalarValue(SQL)
            Return New ScalarResult(oResult)

        Catch ex As FaultException
            _Logger.Error(ex)
            Throw ex

        Catch ex As Exception
            _Logger.Error(ex)
            Throw GetFault(ex)

        End Try
    End Function

    Public Function ExecuteNonQuery_Firebird(SQL As String) As NonQueryResult Implements IEDMIService.ExecuteNonQuery_Firebird
        Try
            _Logger.Info($"ExecuteNonQuery, SQL: {SQL}")
            Dim oResult As Boolean = Firebird.ExecuteNonQuery(SQL)
            Return New NonQueryResult()

        Catch ex As FaultException
            _Logger.Error(ex)
            Throw ex

        Catch ex As Exception
            _Logger.Error(ex)
            Throw GetFault(ex)

        End Try
    End Function
#End Region

#Region "=== Document ==="
    ''' <summary>
    ''' Imports a file according to ObjectStoreId
    ''' </summary>
    ''' <returns></returns>
    'Public Function ImportFile(Data As DocumentImportRequest) As DocumentImportResponse Implements IEDMIService.ImportFile
    '    Dim oObjectStore = GlobalState.ObjectStores.First()
    '    Dim EDMIPath = New EDMI.File.Path(LogConfig, oObjectStore.Path)

    '    ' TODO: 
    '    ' - Get Object Store -> Object Catalog -> Catalog Path
    '    ' - If IS_ARCHIVE = True And RetentionDays <> Nothing:
    '    '   DoArchive!
    '    ' - Refactor EDMIPath to get ObjectStore at service start up
    '    '   and return ObjectStore Path from ObjectStoreId + RelativePath
    '    ' VWIDB_OBJECTSTORE

    '    Dim oRelativePath As String = EDMIPath.GetRelativePath(Data.DocumentType, Data.FileName)
    '    Dim oAbsolutePath As String = EDMIPath.GetFullPath(Data.DocumentType, Data.FileName)
    '    Dim oDirectoryPath = EDMIPath.GetFullPath(Data.DocumentType)

    '    Try
    '        Directory.CreateDirectory(oDirectoryPath)

    '        Dim oVersionedFileName As String = Filesystem.GetVersionedFilename(oAbsolutePath)

    '        _Logger.Info("ImportFile: Saving file [{0}] to path [{1}]", Data.FileName, oVersionedFileName)
    '        Using oStream = New FileStream(oVersionedFileName, FileMode.CreateNew)
    '            oStream.Write(Data.Contents, 0, Data.Contents.Length)
    '            oStream.Flush(True)
    '            oStream.Close()
    '        End Using

    '        ' insert into db
    '        Dim oCommand As New SqlCommand("PRIDB_NEW_DOCUMENT")
    '        oCommand.Parameters.AddWithValue("@OBJ_ST_ID", 1)
    '        oCommand.Parameters.AddWithValue("@REL_PATH", oRelativePath)
    '        oCommand.Parameters.AddWithValue("@WHO", _Username)
    '        oCommand.Parameters.AddWithValue("@REF_DOCID", 0)
    '        oCommand.Parameters.Add(New SqlParameter("@IDB_OBJ_ID", SqlDbType.BigInt))

    '        Dim oObjectId = MSSQL_IDB.GetScalarValue(oCommand, "@IDB_OBJ_ID")

    '        Return New DocumentImportResponse() With {.ObjectId = oObjectId}

    '    Catch ex As FaultException
    '        _Logger.Error(ex)
    '        Throw ex

    '    Catch ex As Exception
    '        _Logger.Error(ex)
    '        Throw GetFault(ex)

    '    End Try
    'End Function

    Public Function GetFileByObjectId(Data As DocumentStreamRequest) As DocumentStreamResponse Implements IEDMIService.GetFileByObjectId
        Try
            Dim oFullPath = GetFullPathForObjectId(Data.ObjectId)

            If oFullPath = String.Empty Then
                Dim oDetails As New ObjectDoesNotExistFault(Data.ObjectId)
                _Logger.Warn("GetFileByObjectId: " & oDetails.ErrorMessage)
                Throw New FaultException(Of ObjectDoesNotExistFault)(oDetails, oDetails.ErrorMessage)
            End If

            _Logger.Debug("GetFileByObjectId: Loading file [{0}]", oFullPath)

            Dim oFileInfo As New FileInfo(oFullPath)

            If Not oFileInfo.Exists Then
                Dim oDetails As New ObjectDoesNotExistFault(Data.ObjectId)
                _Logger.Warn("GetFileByObjectId: " & oDetails.ErrorMessage)
                Throw New FaultException(Of ObjectDoesNotExistFault)(oDetails, oDetails.ErrorMessage)
            End If

            Dim oDestination As New MemoryStream()
            Using oSource As FileStream = IO.File.OpenRead(oFullPath)
                oSource.CopyTo(oDestination)
            End Using
            oDestination.Seek(0, SeekOrigin.Begin)

            Dim oMessage As New DocumentStreamResponse() With {
                .FileName = oFileInfo.Name,
                .FileContents = oDestination
            }

            Return oMessage

        Catch ex As FaultException
            _Logger.Error(ex)
            Throw ex

        Catch ex As Exception
            _Logger.Error(ex)
            Throw GetFault(ex)

        End Try
    End Function

    Public Function GetFileInfoByObjectId(Data As DocumentInfoRequest) As DocumentInfoResponse Implements IEDMIService.GetFileInfoByObjectId
        Try
            _Logger.Debug("GetFileInfoByObjectId...")
            Dim oAccessRight = GetAccessRightForObjectId(Data.UserId, Data.ObjectId)
            Dim oFullPath = GetFullPathForObjectId(Data.ObjectId)

            If oFullPath = String.Empty Then
                Dim oFault As New ObjectDoesNotExistFault(Data.ObjectId)
                Throw New FaultException(Of ObjectDoesNotExistFault)(oFault, oFault.ErrorMessage)
            End If

            Return New DocumentInfoResponse With {
                .FileRight = oAccessRight,
                .FullPath = oFullPath
            }
            _Logger.Debug("End GetFileInfoByObjectId!")
        Catch ex As FaultException
            _Logger.Warn("GetFileInfoByObjectId: " & ex.Message)
            _Logger.Error(ex)
            Throw ex

        Catch ex As Exception
            _Logger.Warn("GetFileInfoByObjectId: " & ex.Message)
            _Logger.Error(ex)
            Throw GetFault(ex)

        End Try
    End Function

    Public Function ListFilesForUser() As DocumentListResponse Implements IEDMIService.ListFilesForUser
        Try
            Dim oDatatable = GetFileList()

            Return New DocumentListResponse() With {
                .Datatable = oDatatable
            }

        Catch ex As FaultException
            _Logger.Error(ex)
            Throw ex

        Catch ex As Exception
            _Logger.Error(ex)
            Throw GetFault(ex)

        End Try
    End Function

    Public Function GetFileList() As DataTable
        Try
            If Not GlobalState.TableStore.Tables.Contains(TBIDB_DOC_INFO) Then
                _Logger.Warn("GetFileList: Document info table does not exist!")
                Return Nothing
            End If

            Dim oTable As DataTable = GlobalState.TableStore.Tables.Item(TBIDB_DOC_INFO)
            Return oTable
        Catch ex As Exception
            _Logger.Warn("GetFileList: Unexpected Error while getting file list.")
            _Logger.Error(ex)
            Return Nothing
        End Try
    End Function

    Public Function TestObjectIdExists(Data As TestObjectIdExistsRequest) As TestObjectIdExistsResponse Implements IEDMIService.TestObjectIdExists
        Try
            Dim oDeleted As Boolean = False
            Dim oActive As Boolean = False

            If _IDBHelpers.TestObjectIdExists(Data.ObjectId, oDeleted, oActive) = False Then
                Return New TestObjectIdExistsResponse(False)
            End If

            Return New TestObjectIdExistsResponse(True) With {
                .Deleted = oDeleted,
                .Inactive = Not oActive
            }
        Catch ex As Exception
            _Logger.Error(ex)
            Return New TestObjectIdExistsResponse(False)

        End Try
    End Function




#End Region

#Region "=== Private ==="





    Private Function GetFault(Exception As Exception) As FaultException
        Dim oFault As New UnexpectedErrorFault(Exception)
        Return New FaultException(Of UnexpectedErrorFault)(oFault, New FaultReason(oFault.InnerException.Message))
    End Function

    Private Function GetFault(ErrorMessage As String) As FaultException
        Dim oFault As New UnexpectedErrorFault(New ApplicationException(ErrorMessage))
        Return New FaultException(Of UnexpectedErrorFault)(oFault, New FaultReason(oFault.InnerException.Message))
    End Function

    Private Function GetFullPathForObjectId(ObjectId As Long) As String
        Try
            If Not GlobalState.TableStore.Tables.Contains(TBIDB_DOC_INFO) Then
                _Logger.Warn("GetFullPathForObjectId: Document info table does not exist!")
                Return String.Empty
            End If

            Dim oTable As DataTable = GlobalState.TableStore.Tables.Item(TBIDB_DOC_INFO)
            Dim oRows As List(Of DataRow) = oTable.Select($"IDB_OBJ_ID = {ObjectId}").ToList()

            Dim oFullPath As String

            If oRows.Count = 0 Then
                _Logger.Warn("GetFullPathForObjectId: Full path does not exist for object [{0}]", ObjectId)
                oFullPath = String.Empty
            Else
                Dim oRow As DataRow = oRows.First()
                oFullPath = oRow.Item("FULL_PATH")
            End If

            Return oFullPath
        Catch ex As Exception
            _Logger.Warn("GetFullPathForObjectId: Unexpected Error while getting full path for object [{0}].", ObjectId)
            _Logger.Error(ex)
            Return String.Empty
        End Try
    End Function

    Private Function GetAccessRightForObjectId(UserId As Long, ObjectId As Long) As AccessRight
        Try
            If Not GlobalState.TableStore.Tables.Contains(TBIDB_ACCESSRIGHT) Then
                _Logger.Warn("GetAccessRightForObjectId: Access right table does not exist!")
                Return AccessRight.VIEW_ONLY
            End If

            _Logger.Debug("Getting AccessRights for ObjectId [{0}]", ObjectId)
            Dim oTable As DataTable = GlobalState.TableStore.Tables.Item(TBIDB_ACCESSRIGHT)
            Dim oRows As List(Of DataRow) = oTable.Select($"IDB_OBJ_ID = {ObjectId} AND USR_ID = {UserId}").ToList()
            Dim oRight As AccessRight

            _Logger.Debug("Successfully got AccessRights for ObjectId [{0}]", ObjectId)
            _Logger.Debug("Parsing AccessRights for ObjectId [{0}]", ObjectId)

            If oRows.Count = 0 Then
                _Logger.Warn("GetAccessRightForObjectId: Access right assignment does not exist for user [{0}] on object [{1}]", UserId, ObjectId)
                Return AccessRight.VIEW_ONLY
            Else
                If oRows.Count > 1 Then
                    _Logger.Warn("GetAccessRightForObjectId: More than one access right assignment found for user [{0}] on object [{1}]", UserId, ObjectId)
                End If

                Dim oRow As DataRow = oRows.First()
                Dim oRightAsInt = oRow.Item("ACCESSRIGHT")

                oRight = Utils.ToEnum(Of AccessRight)(oRightAsInt)
            End If

            _Logger.Debug("Successfully parsed AccessRights for ObjectId [{0}]", ObjectId)
            _Logger.Debug("AccessRight for ObjectId [{0}] is [{1}]", ObjectId, oRight.ToString)

            Return oRight
        Catch ex As Exception
            _Logger.Warn("GetAccessRightForObjectId: Unexpected Error while getting access right for object [{0}].", ObjectId)
            _Logger.Error(ex)
            Return AccessRight.VIEW_ONLY
        End Try
    End Function

    Private Function StripDomainFromUsername(UserName As String)
        If UserName.Contains("\") Then
            Return UserName.Split("\")(1)
        ElseIf UserName.Contains("@") Then
            Return UserName.Split("@")(0)
        Else
            Return UserName
        End If
    End Function


#End Region


End Class