Imports System.ServiceModel
Imports DigitalData.Modules.Database
Imports DigitalData.Modules.Logging
Imports DigitalData.Modules.Filesystem
Imports DigitalData.Services.IDBService
Imports System.IO

<ServiceBehavior(InstanceContextMode:=InstanceContextMode.PerSession)>
Public Class IDBService
    Implements IIDBService

    Public Shared LogConfig As LogConfig
    Public Shared Database As Firebird
    Public Shared AppConfig As AppConfig

    Private ReadOnly _logger As Logger

    Private _request As Request = Nothing
    Private _debug As Boolean = False
    Private _username As String

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

        _username = oUsername
        _logger = LogConfig.GetLogger()
    End Sub

#Region "Auth"
    Private Function TestUserAuth() As Boolean
        Try
            'Dim oSQL As String = $"SELECT FNIDB_AUTH_USER('{_username}') FROM RDB$DATABASE;"
            'Dim oResult As Boolean = Database.GetScalarValue(oSQL)
            'Return oResult
            Return True
        Catch ex As Exception
            _logger.Error(ex)
            Return False
        End Try
    End Function
#End Region
#Region "Heartbeat"
    Public Function Heartbeat() As Boolean Implements IIDBService.Heartbeat
        Return True
    End Function
#End Region
#Region "Request"
    Public Sub CreateRequest(Name As String, Optional Debug As Boolean = False)
        _request = New Request(Name, _username, Database, Debug)
        _debug = Debug

        _logger.Info("Creating request {0}/{1}", _request.Name, _request.RequestId)
    End Sub

    Public Function CreateDatabaseRequest(Name As String, Optional Debug As Boolean = False) As String Implements IIDBService.CreateDatabaseRequest
        CreateRequest(Name, Debug)

        Return _request.Name
    End Function

    Public Sub CloseDatabaseRequest() Implements IIDBService.CloseDatabaseRequest
        If IsNothing(_request) Then
            Exit Sub
        End If

        _logger.Info("Closing request {0}", _request.ToString)
        _request.Connection.Close()
        _request = Nothing
    End Sub

#End Region
#Region "Database"
    Private Sub TestRequestCreated()
        If IsNothing(_request) Then
            Throw New Exceptions.NoRequestException()
        End If
    End Sub

    Public Function ReturnDatatable(SQL As String) As TableResult Implements IIDBService.ReturnDatatable
        Try
            TestRequestCreated()

            _logger.Info($"ReturnDatatable, SQL: {SQL}")
            _request.LogDebug($"ReturnDatatable, SQL: {SQL}")

            Dim oResult As DataTable = Database.GetDatatableWithConnection(SQL, _request.Connection)
            Return New TableResult(oResult)
        Catch ex As Exception
            _logger.Error(ex)
            _request.LogError(ex.Message)
            Return New TableResult(ex.Message)
        End Try
    End Function

    Public Function ReturnScalar(SQL As String) As ScalarResult Implements IIDBService.ReturnScalar
        Try
            TestRequestCreated()

            _logger.Info($"ReturnScalar, SQL: {SQL}")
            _request.LogDebug($"ReturnScalar, SQL: {SQL}")

            Dim oResult As Object = Database.GetScalarValueWithConnection(SQL, _request.Connection)
            Return New ScalarResult(oResult)
        Catch ex As Exception
            _logger.Error(ex)
            _request.LogError(ex.Message)
            Return New ScalarResult(ex.Message)
        End Try
    End Function

    Public Function ExecuteNonQuery(SQL As String) As NonQueryResult Implements IIDBService.ExecuteNonQuery
        Try
            TestRequestCreated()

            _logger.Info($"ExecuteNonQuery, SQL: {SQL}")
            _request.LogDebug($"ExecuteNonQuery, SQL: {SQL}")

            Dim oResult As Boolean = Database.ExecuteNonQueryWithConnection(SQL, _request.Connection)
            Return New NonQueryResult()
        Catch ex As Exception
            _logger.Error(ex)
            _request.LogError(ex.Message)
            Return New NonQueryResult(ex.Message)
        End Try
    End Function


#End Region
#Region "Document (with FileContainer)"
    Public Function NewFile(FileName As String, Contents() As Byte) As DocumentResult Implements IIDBService.NewFile
        Try
            Dim oContainer As FileContainer
            Dim oContainerId As String

            If Not TestUserAuth() Then
                Throw New Exception($"User {_username} not authorized.")
            End If

            oContainer = FileContainer.Create(LogConfig, AppConfig.ContainerPassword)
            oContainerId = oContainer.ContainerId
            _logger.Debug("Container created with id {0}", oContainerId)

            Dim oExtension As String = Path.GetExtension(FileName).Substring(1)
            _logger.Debug("File extension of file {0} is {1}", FileName, oExtension)

            Dim oSQL = $"SELECT FNICM_NEW_DOC('010', '{oContainerId}', '{GetContainerName(oContainerId)}', '{FileName}', '{oExtension}', '{_username}') FROM RDB$DATABASE;"
            Dim oDocId As Int64 = Database.GetScalarValue(oSQL)

            If oDocId = -1 Then
                _logger.Warn("Database returned -1 while creating Document Entry. File was not saved!")
                Return Nothing
            End If

            _logger.Debug("Database Entry created with DocId {0}", oDocId)

            oContainer.SetFile(Contents, FileName)
            oContainer.SaveAs(GetContainerPath(oContainerId))

            _logger.Debug("File saved in Container!", FileName)

            Dim oDocument = New DocumentObject(oContainerId, oDocId, FileName)
            Return New DocumentResult(oDocument)
        Catch ex As Exception
            _logger.Error(ex)
            Return New DocumentResult(ex.Message)
        End Try
    End Function

    Public Function UpdateFile(DocObject As DocumentObject, Contents() As Byte) As DocumentResult Implements IIDBService.UpdateFile
        Try
            TestFileExists(DocObject.ContainerId)

            ' TODO: update db

            Dim oFilePath = GetContainerPath(DocObject.ContainerId)
            Dim oFileContainer As FileContainer = FileContainer.Load(LogConfig, AppConfig.ContainerPassword, oFilePath)

            oFileContainer.SetFile(Contents, oFileContainer.GetFile.FileName)
            oFileContainer.Save()


            Return New DocumentResult(DocObject)
        Catch ex As Exception
            _logger.Error(ex)
            Return Nothing
        End Try
    End Function

    Public Function GetFile(DocObject As DocumentObject) As DocumentResult Implements IIDBService.GetFile
        Try
            TestFileExists(DocObject.ContainerId)

            Dim oContainerPath = GetContainerPath(DocObject.ContainerId)
            Dim oContainer As FileContainer = FileContainer.Load(LogConfig, AppConfig.ContainerPassword, oContainerPath)
            Dim oContents As Byte() = oContainer.GetFile().Contents

            Return New DocumentResult(DocObject, oContents)
        Catch ex As Exception
            _logger.Error(ex)
            Return New DocumentResult(ex.Message)
        End Try
    End Function

    Public Function DeleteFile(DocObject As DocumentObject) As Boolean Implements IIDBService.DeleteFile
        Try
            TestFileExists(DocObject.ContainerId)

            Dim oFilePath = GetContainerPath(DocObject.ContainerId)
            IO.File.Delete(oFilePath)

            'TODO: Delete doc from db

            Return True
        Catch ex As Exception
            _logger.Error(ex)
            Return False
        End Try
    End Function

    Private Function GetContainerPath(ContainerId As String) As String
        Return Path.Combine(AppConfig.ContainerPath, GetContainerName(ContainerId))
    End Function

    Private Function GetContainerName(ContainerId As String) As String
        Return ContainerId & ".enc"
    End Function

    Private Sub TestFileExists(ContainerId)
        Dim oContainerPath = GetContainerPath(ContainerId)

        If Not IO.File.Exists(oContainerPath) Then
            Throw New FileNotFoundException("Container existiert nicht", oContainerPath)
        End If
    End Sub
#End Region

#Region "Document"
    Public Function ImportFile(FileInfo As FileInfo, Contents() As Byte, [Readonly] As Boolean, RetentionPeriod As Integer) As DocumentResult2 Implements IIDBService.ImportFile
        Dim oFilePath = Path.Combine(AppConfig.ContainerPath, FileInfo.Name)
        Dim oDocument = New DocumentResult2.DocumentObject() With {.FileName = FileInfo.Name}

        Try
            _logger.Info("Saving file [{0}] to path [{1}]", FileInfo.Name, oFilePath)
            Using oStream = New FileStream(oFilePath, FileMode.CreateNew)
                oStream.Write(Contents, 0, Contents.Length)
                oStream.Flush(True)
                oStream.Close()
            End Using

            Dim oAttributes = IO.File.GetAttributes(oFilePath) Or FileAttributes.ReadOnly

            If RetentionPeriod Then
                _logger.Info("Setting LastAccessTime")
                IO.File.SetLastAccessTime(oFilePath, Date.Now.AddYears(30))
            End If

            If [Readonly] Then
                _logger.Info("Setting ReadOnly Attribute")
                IO.File.SetAttributes(oFilePath, oAttributes)
            End If

            Return New DocumentResult2(oDocument)
        Catch ex As Exception
            _logger.Error(ex)
            Return New DocumentResult2(ex.Message)
        End Try
    End Function
#End Region

#Region "Utils"
    Public Function GetDocumentByDocumentId(DocumentId As Long) As DocumentResult Implements IIDBService.GetDocumentByDocumentId
        Try
            Dim oSQL = $"SELECT GUID, CONTAINER_ID, ORIGINAL_FILENAME FROM TBIDB_DOCUMENT WHERE GUID = {DocumentId}"
            Dim oTable = Database.GetDatatable(oSQL)

            If oTable.Rows.Count = 0 Then
                Return New DocumentResult("Document not found")
            End If

            Dim oRow As DataRow = oTable.Rows.Item(0)
            Dim oDocument As New DocumentObject(
                oRow.Item("CONTAINER_ID"),
                oRow.Item("GUID"),
                oRow.Item("ORIGINAL_FILENAME")
            )

            TestFileExists(oDocument.ContainerId)

            Dim oContainerPath = GetContainerPath(oDocument.ContainerId)
            Dim oContainer As FileContainer = FileContainer.Load(LogConfig, AppConfig.ContainerPassword, oContainerPath)
            Dim oContents As Byte() = oContainer.GetFile().Contents

            Return New DocumentResult(oDocument, oContents)
        Catch ex As Exception
            Return New DocumentResult(ex.Message)
        End Try
    End Function

    Public Function GetDocumentByContainerId(ContainerId As String) As DocumentResult Implements IIDBService.GetDocumentByContainerId
        Try
            Dim oSQL = $"SELECT GUID, CONTAINER_ID, ORIGINAL_FILENAME FROM TBIDB_DOCUMENT WHERE CONTAINER_ID = '{ContainerId}'"
            Dim oTable = Database.GetDatatable(oSQL)

            If oTable.Rows.Count = 0 Then
                Return New DocumentResult("Document not found")
            End If

            Dim oRow As DataRow = oTable.Rows.Item(0)
            Dim oDocument As New DocumentObject(
                oRow.Item("CONTAINER_ID"),
                oRow.Item("GUID"),
                oRow.Item("ORIGINAL_FILENAME")
            )

            TestFileExists(oDocument.ContainerId)

            Dim oContainerPath = GetContainerPath(oDocument.ContainerId)
            Dim oContainer As FileContainer = FileContainer.Load(LogConfig, AppConfig.ContainerPassword, oContainerPath)
            Dim oContents As Byte() = oContainer.GetFile().Contents

            Return New DocumentResult(oDocument, oContents)
        Catch ex As Exception
            Return New DocumentResult(ex.Message)
        End Try
    End Function
#End Region

#Region "Index"
    Public Function NewFileIndex(DocObject As DocumentObject, Syskey As String, LanguageCode As String, Value As String) As IndexResult Implements IIDBService.NewFileIndex
        Try
            Dim oSQL = $"SELECT FNIDB_NEW_DOC_VALUE({DocObject.DocumentId},'{Syskey}','{LanguageCode}','{Value}','{_username}') FROM RDB$DATABASE;"
            Dim oIndexId As Int64 = Database.GetScalarValue(oSQL)

            Return New IndexResult(oIndexId)
        Catch ex As Exception
            _logger.Error(ex)
            Return New IndexResult(ex.Message)
        End Try
    End Function
#End Region
End Class