Imports System.Net
Imports System.Net.Sockets
Imports System.Threading
Imports System.Text
Imports System.Text.Json
Imports System.Globalization
Imports Microsoft.Data.SqlClient
Imports System.IO
Imports zkemkeeper
Imports Azure.Core

Public Class Form1
    Private listener As TcpListener
    Private cts As CancellationTokenSource
    Private machineNumberSetting As Integer = 1
    Private commKeySetting As Integer = 0

    Private Async Sub btnStart_Click(sender As Object, e As EventArgs) Handles btnStart.Click
        Dim ip As IPAddress = Nothing
        If Not IPAddress.TryParse(txtAddress.Text.Trim(), ip) Then
            MessageBox.Show("Địa chỉ IP không hợp lệ.", "Lỗi", MessageBoxButtons.OK, MessageBoxIcon.Error)
            Return
        End If

        Dim port As Integer
        If Not Integer.TryParse(txtPort.Text.Trim(), port) OrElse port <= 0 OrElse port > 65535 Then
            MessageBox.Show("Port phải là số từ 1-65535.", "Lỗi", MessageBoxButtons.OK, MessageBoxIcon.Error)
            Return
        End If

        machineNumberSetting = SafeParseInt(txtMachine.Text.Trim(), 1)
        commKeySetting = SafeParseInt(txtCommKey.Text.Trim(), 0)

        StopListener()

        Try
            listener = New TcpListener(ip, port)
            listener.Start()
            cts = New CancellationTokenSource()
            lblStatus.Text = $"Trạng thái: Đang lắng nghe {ip}:{port}"
            btnStart.Enabled = False
            btnStop.Enabled = True
            Await RunListenerAsync(cts.Token)
        Catch ex As Exception
            lblStatus.Text = "Trạng thái: Lỗi khi khởi động"
            MessageBox.Show($"Không thể khởi động server: {ex.Message}", "Lỗi", MessageBoxButtons.OK, MessageBoxIcon.Error)
        Finally
            btnStart.Enabled = True
            btnStop.Enabled = False
        End Try
    End Sub

    Private Async Function RunListenerAsync(token As CancellationToken) As Task
        Try
            While Not token.IsCancellationRequested
                Dim client = Await listener.AcceptTcpClientAsync()
                Dim _unused = HandleClientAsync(client, token) ' fire-and-forget
            End While
        Catch ex As ObjectDisposedException
            ' Listener đã dừng, bỏ qua.
        Catch ex As Exception
            If Not token.IsCancellationRequested Then
                MessageBox.Show($"Lỗi khi lắng nghe: {ex.Message}", "Lỗi", MessageBoxButtons.OK, MessageBoxIcon.Error)
                lblStatus.Text = "Trạng thái: Lỗi khi lắng nghe"
            End If
        End Try
    End Function

    Private Sub StopListener()
        Try
            If cts IsNot Nothing Then
                cts.Cancel()
                cts.Dispose()
                cts = Nothing
            End If

            If listener IsNot Nothing Then
                listener.Stop()
                listener = Nothing
            End If

            lblStatus.Text = "Trạng thái: Dừng"
            btnStart.Enabled = True
            btnStop.Enabled = False
        Catch
            ' Bỏ qua lỗi khi dừng.
        End Try
    End Sub

    Private Sub btnStop_Click(sender As Object, e As EventArgs) Handles btnStop.Click
        StopListener()
    End Sub

    Private Sub Form1_FormClosing(sender As Object, e As FormClosingEventArgs) Handles Me.FormClosing
        StopListener()
    End Sub

    Private Async Function HandleClientAsync(client As TcpClient, token As CancellationToken) As Task
        Using client
            Try
                Using stream = client.GetStream()
                    Dim buffer(1023) As Byte
                    Dim read = Await stream.ReadAsync(buffer, 0, buffer.Length, token)
                    If read > 0 Then
                        Dim msg = Encoding.UTF8.GetString(buffer, 0, read).Trim()
                        Dim request As AttendanceRequest = Nothing
                        Dim isOk As Boolean = False
                        Dim workingLinePosition As Integer = -1
                        Dim parseSuccess = TryParsePayload(msg, request)
                        Dim requestType = If(parseSuccess AndAlso request IsNot Nothing, request.Type, GetTypeFromJson(msg))

                        ' Kiểm tra nếu Type = 1 thì phải có đầy đủ thông tin
                        If requestType = "1" Then
                            If Not parseSuccess OrElse request Is Nothing Then
                                ' Parse thất bại nhưng Type = 1 - thử parse lại để lấy thông tin chi tiết
                                isOk = False
                                Dim errorDetail = $"Lỗi: Không thể parse payload hoặc thiếu thông tin bắt buộc." & Environment.NewLine &
                                                 $"Payload nhận được: {msg}" & Environment.NewLine &
                                                 $"Parse thành công: {parseSuccess}, Request Is Nothing: {request Is Nothing}"
                                UpdateStatusWithColor(errorDetail, Color.FromArgb(&HF22C3F))
                                SetStatusLabelSafe("Trạng thái: Lỗi - Thiếu thông tin bắt buộc", Color.FromArgb(&HF22C3F))
                            Else
                                ' Validate các trường bắt buộc cho Type = 1
                                If String.IsNullOrWhiteSpace(request.Name) OrElse
                                   String.IsNullOrWhiteSpace(request.DeviceIp) OrElse
                                   request.DevicePort <= 0 OrElse
                                   request.FromDate = Nothing OrElse
                                   request.ToDate = Nothing Then
                                    isOk = False
                                    Dim errorMsg = $"Lỗi: Type=1 yêu cầu đầy đủ thông tin (name, ip, port, from, to). Nhận được: {msg}"
                                    UpdateStatusWithColor(errorMsg, Color.FromArgb(&HF22C3F))
                                    SetStatusLabelSafe("Trạng thái: Lỗi - Thiếu thông tin bắt buộc", Color.FromArgb(&HF22C3F))
                                Else
                                    Dim msgText = $"Bắt đầu tải dữ liệu từ {request.Name} ({request.DeviceIp}:{request.DevicePort}) từ {request.FromDate:dd-MM-yyyy} đến {request.ToDate:dd-MM-yyyy}."
                                    UpdateStatusWithColor(msgText, Color.FromArgb(&H86D9F0))
                                    ' Hiển thị "Máy chấm công đang làm việc" trong status label
                                    SetStatusLabelSafe("Trạng thái: Máy chấm công " & request.Name & " đang xử lý dữ liệu...", Color.FromArgb(&H2C61F2))
                                    isOk = True
                                End If
                            End If
                        ElseIf Not String.IsNullOrWhiteSpace(requestType) Then
                            ' Type != 1 thì trả về FALSE
                            isOk = False
                            Dim errorMsg = $"Lỗi: Nhận được Type={requestType} không hỗ trợ. Payload: {msg}"
                            UpdateStatusWithColor(errorMsg, Color.FromArgb(&HF22C3F))
                        Else
                            ' Không parse được Type
                            isOk = False
                            Dim errorMsg = $"Lệnh không đúng cấu trúc: {msg}"
                            UpdateStatusWithColor(errorMsg, Color.FromArgb(&HF22C3F))
                        End If
                        If isOk AndAlso request IsNot Nothing AndAlso request.Type = "1" Then
                            ' Chỉ xử lý khi Type = 1 và đầy đủ thông tin
                            If request.Delete = 1 Then
                                ' Xóa dữ liệu trong bảng LogATT trước khi tải dữ liệu mới
                                Try
                                    DeleteLogs()
                                    Dim deleteMsg = "Thành công! Đã xóa dữ liệu cũ trong bảng LogATT."
                                    UpdateStatusWithColor(deleteMsg, Color.FromArgb(&H2FF22C))
                                Catch ex As Exception
                                    Dim errorMsg = $"Lỗi khi xóa dữ liệu: {ex.Message}"
                                    UpdateStatusWithColor(errorMsg, Color.FromArgb(&HF22C3F))
                                    isOk = False
                                End Try
                            End If
                            If isOk Then
                                isOk = Await ProcessAttendanceAsync(request, token)
                            End If

                        End If

                        ' Reset status label sau khi trả kết quả
                        If requestType = "1" AndAlso isOk Then
                            SetStatusLabelSafe("Trạng thái: Đang chờ lệnh kế tiếp...", Color.Lime)
                        ElseIf requestType = "1" AndAlso Not isOk Then
                            ' Giữ nguyên thông báo lỗi
                        ElseIf requestType <> "1" AndAlso Not String.IsNullOrWhiteSpace(requestType) Then
                            SetStatusLabelSafe("Trạng thái: Đang chờ lệnh kế tiếp...", Color.Lime)
                        End If

                        Dim responseText = If(isOk, "TRUE", "FALSE")
                        Dim respBytes = Encoding.UTF8.GetBytes(responseText)
                        Await stream.WriteAsync(respBytes, 0, respBytes.Length, token)
                    Else
                        UpdateStatusSafe("Có kết nối tới rồi đóng ngay.")
                    End If
                End Using
            Catch ex As Exception
                If Not token.IsCancellationRequested Then
                    UpdateStatusSafe($"Lỗi client: {ex.Message}")
                End If
            End Try
        End Using
    End Function

    Private Sub UpdateStatusSafe(text As String)
        UpdateStatusWithColor(text, Color.Lime)
    End Sub

    Private Sub UpdateStatusWithColor(text As String, textColor As Color)
        If Me.InvokeRequired Then
            Me.BeginInvoke(New Action(Of String, Color)(AddressOf UpdateStatusWithColor), text, textColor)
        Else
            lblStatus.Text = text
            LogMessageWithColor(text, textColor)
        End If
    End Sub

    Private Sub SetStatusLabelSafe(text As String, textColor As Color)
        If Me.InvokeRequired Then
            Me.BeginInvoke(New Action(Of String, Color)(AddressOf SetStatusLabelSafe), text, textColor)
        Else
            lblStatus.Text = text
            lblStatus.ForeColor = textColor
        End If
    End Sub

    Private Sub LogMessageSafe(text As String)
        LogMessageWithColor(text, Color.Lime)
    End Sub

    Private Sub LogMessageWithColor(text As String, textColor As Color)
        If Me.InvokeRequired Then
            Me.BeginInvoke(New Action(Of String, Color)(AddressOf LogMessageWithColor), text, textColor)
        Else
            If rtbLog Is Nothing Then Return
            Dim timestamp = $"[{DateTime.Now:dd-MM-yyyy HH:mm:ss}] "
            rtbLog.SelectionStart = rtbLog.TextLength
            rtbLog.SelectionLength = 0
            rtbLog.SelectionColor = textColor
            rtbLog.AppendText(timestamp)
            rtbLog.SelectionStart = rtbLog.TextLength
            rtbLog.SelectionLength = 0
            rtbLog.SelectionColor = textColor
            rtbLog.AppendText($"{text}{Environment.NewLine}")
            rtbLog.SelectionStart = rtbLog.TextLength
            rtbLog.ScrollToCaret()
        End If
    End Sub

    Private Function LogMessageWithColorReturnPosition(text As String, textColor As Color) As Integer
        If Me.InvokeRequired Then
            Dim result As Integer = -1
            Me.Invoke(Sub()
                          result = LogMessageWithColorReturnPosition(text, textColor)
                      End Sub)
            Return result
        Else
            If rtbLog Is Nothing Then Return -1
            Dim startPos = rtbLog.TextLength
            Dim timestamp = $"[{DateTime.Now:dd-MM-yyyy HH:mm:ss}] "
            rtbLog.SelectionStart = startPos
            rtbLog.SelectionLength = 0
            rtbLog.SelectionColor = textColor
            rtbLog.AppendText(timestamp)
            rtbLog.SelectionStart = rtbLog.TextLength
            rtbLog.SelectionLength = 0
            rtbLog.SelectionColor = textColor
            rtbLog.AppendText($"{text}{Environment.NewLine}")
            rtbLog.SelectionStart = rtbLog.TextLength
            rtbLog.ScrollToCaret()
            Return startPos
        End If
    End Function

    Private Sub RemoveLogLine(startPosition As Integer)
        If Me.InvokeRequired Then
            Me.BeginInvoke(New Action(Of Integer)(AddressOf RemoveLogLine), startPosition)
        Else
            If rtbLog Is Nothing OrElse startPosition < 0 OrElse startPosition >= rtbLog.TextLength Then Return
            Dim lineEnd = rtbLog.Text.IndexOf(Environment.NewLine, startPosition)
            If lineEnd < 0 Then lineEnd = rtbLog.TextLength
            Dim lineLength = lineEnd - startPosition + Environment.NewLine.Length
            rtbLog.SelectionStart = startPosition
            rtbLog.SelectionLength = lineLength
            rtbLog.SelectedText = ""
            rtbLog.SelectionStart = rtbLog.TextLength
            rtbLog.ScrollToCaret()
        End If
    End Sub

    Private Function GetTypeFromJson(jsonText As String) As String
        Try
            Dim normalized = jsonText.Trim()
            If normalized.Contains("'"c) AndAlso Not normalized.Contains(""""c) Then
                normalized = normalized.Replace("'", """")
            End If
            Dim doc = JsonDocument.Parse(normalized)
            Dim root = doc.RootElement
            Return GetString(root, "type")
        Catch
            Return ""
        End Try
    End Function

    Private Function TryParsePayload(jsonText As String, ByRef result As AttendanceRequest) As Boolean
        Try
            ' Một số client gửi JSON dùng dấu nháy đơn, chuẩn hóa sang nháy kép
            Dim normalized = jsonText.Trim()
            If normalized.Contains("'"c) AndAlso Not normalized.Contains(""""c) Then
                normalized = normalized.Replace("'", """")
            End If

            Dim doc = JsonDocument.Parse(normalized)
            Dim root = doc.RootElement
            Dim typeVal = GetString(root, "type")
            Dim ipVal = GetString(root, "ip")
            Dim portVal = GetInt(root, "port")
            Dim nameVal = GetString(root, "name")
            Dim fromVal = GetString(root, "from")
            Dim toVal = GetString(root, "to")

            If String.IsNullOrWhiteSpace(typeVal) Then Return False
            If String.IsNullOrWhiteSpace(ipVal) Then Return False
            If portVal <= 0 Then Return False
            If String.IsNullOrWhiteSpace(nameVal) Then Return False
            If String.IsNullOrWhiteSpace(fromVal) Then Return False
            If String.IsNullOrWhiteSpace(toVal) Then Return False

            Dim formats = {"dd/MM/yyyy", "dd-MM-yyyy"}
            Dim fromDate As DateTime
            Dim toDate As DateTime
            Dim deleteVal = GetInt(root, "delete")
            If Not DateTime.TryParseExact(fromVal, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, fromDate) Then Return False
            If Not DateTime.TryParseExact(toVal, formats, CultureInfo.InvariantCulture, DateTimeStyles.None, toDate) Then Return False

            result = New AttendanceRequest With {
                .Type = typeVal,
                .DeviceIp = ipVal,
                .DevicePort = portVal,
                .Name = nameVal,
                .FromDate = fromDate,
                .ToDate = toDate,
                .Delete = deleteVal
            }

            Return True
        Catch
            Return False
        End Try
    End Function

    Private Function GetString(obj As JsonElement, prop As String) As String
        If obj.TryGetProperty(prop, Nothing) Then
            Dim val As JsonElement
            If obj.TryGetProperty(prop, val) AndAlso val.ValueKind = JsonValueKind.String Then
                Return val.GetString()
            End If
        End If
        Return ""
    End Function

    Private Function GetInt(obj As JsonElement, prop As String) As Integer
        Dim val As JsonElement
        If obj.TryGetProperty(prop, val) Then
            If val.ValueKind = JsonValueKind.Number Then
                Return val.GetInt32()
            ElseIf val.ValueKind = JsonValueKind.String Then
                Dim s = val.GetString()
                Dim n As Integer
                If Integer.TryParse(s, n) Then Return n
            End If
        End If
        Return 0
    End Function

    ' Kết nối máy chấm công và lưu DB
    Private Async Function ProcessAttendanceAsync(req As AttendanceRequest, token As CancellationToken) As Task(Of Boolean)
        Return Await RunOnStaThreadAsync(Function()
                                             Try
                                                 Dim records = ReadLogsFromDevice(req)
                                                 If records.Count = 0 Then
                                                     Dim noDataMsg = "Không có dữ liệu chấm công trong khoảng được yêu cầu."
                                                     UpdateStatusWithColor(noDataMsg, Color.FromArgb(&HF22CE5))
                                                     Return True ' vẫn trả TRUE vì đã xử lý xong
                                                 End If

                                                 InsertLogs(records)
                                                 Dim successMsg = $"Thành công! Đã lấy {records.Count} log và lưu DB."
                                                 UpdateStatusWithColor(successMsg, Color.FromArgb(&H2FF22C))
                                                 Return True
                                             Catch ex As Exception
                                                 Dim errorDetail = $"Lỗi xử lý: {ex.Message}" & Environment.NewLine &
                                                                 $"Thông tin request nhận được:" & Environment.NewLine &
                                                                 $"- Type: {req.Type}" & Environment.NewLine &
                                                                 $"- Name: {req.Name}" & Environment.NewLine &
                                                                 $"- Device IP: {req.DeviceIp}" & Environment.NewLine &
                                                                 $"- Device Port: {req.DevicePort}" & Environment.NewLine &
                                                                 $"- From Date: {req.FromDate:dd-MM-yyyy}" & Environment.NewLine &
                                                                 $"- To Date: {req.ToDate:dd-MM-yyyy}"
                                                 UpdateStatusWithColor(errorDetail, Color.FromArgb(&HF22C3F))
                                                 Return False
                                             End Try
                                         End Function)
    End Function

    Private Function ReadLogsFromDevice(req As AttendanceRequest) As List(Of AttendanceRecord)
        ' Dùng early-binding với Interop.zkemkeeper (đã reference COM)
        Dim zk As CZKEM = CreateZkemInstance()
        Dim machineNumber As Integer = If(machineNumberSetting >= 0, machineNumberSetting, 1)

        Dim connected As Boolean = False
        Try
            connected = zk.Connect_Net(req.DeviceIp, req.DevicePort)
            If Not connected Then
                Dim err As Integer = 0
                zk.GetLastError(err)
                Throw New Exception($"Kết nối máy chấm công thất bại. Mã lỗi: {err}")
            End If

            Dim lastError As Integer = 0
            Dim allRecords As New List(Of AttendanceRecord)

            ' Set comm key nếu có
            If commKeySetting > 0 Then
                Dim ok = zk.SetCommPassword(commKeySetting)
                If Not ok Then
                    zk.GetLastError(lastError)
                    Throw New Exception($"SetCommPassword thất bại. Mã lỗi: {lastError}")
                End If
            End If

            zk.EnableDevice(machineNumber, False)

            ' Đọc dữ liệu vào bộ nhớ trước khi duyệt
            Dim readOk = zk.ReadAllGLogData(machineNumber)
            If Not readOk Then
                zk.GetLastError(lastError)
                Throw New Exception($"ReadAllGLogData thất bại (machine {machineNumber}), mã lỗi: {lastError}")
            End If

            Dim dwEnrollNumber As Integer
            Dim dwVerifyMode As Integer
            Dim dwInOutMode As Integer
            Dim dwYear As Integer
            Dim dwMonth As Integer
            Dim dwDay As Integer
            Dim dwHour As Integer
            Dim dwMinute As Integer
            Dim dwSec As Integer
            Dim dwWorkCode As Integer

            While zk.SSR_GetGeneralLogData(machineNumber,
                                           dwEnrollNumber, dwVerifyMode, dwInOutMode,
                                           dwYear, dwMonth, dwDay, dwHour, dwMinute, dwSec, dwWorkCode)

                Dim ts As New DateTime(dwYear, dwMonth, dwDay, dwHour, dwMinute, dwSec)
                If ts.Date >= req.FromDate.Date AndAlso ts.Date <= req.ToDate.Date Then
                    allRecords.Add(New AttendanceRecord With {
                        .MCC = machineNumber,
                        .MasoNV = dwEnrollNumber,
                        .DateTimeVal = ts,
                        .DateIn = ts.Date,
                        .Lan = dwInOutMode,
                        .TimeIn = ts.TimeOfDay,
                        .AttName = req.Name
                    })
                End If
            End While

            If allRecords.Count = 0 Then
                zk.GetLastError(lastError)
                Throw New Exception($"Không đọc được log (mã lỗi: {lastError}). Thử kiểm tra Machine ID, IP/port, firewall.")
            End If

            Return allRecords
        Finally
            If connected Then
                Try
                    Try
                        zk.EnableDevice(machineNumber, True)
                    Catch
                    End Try
                    zk.Disconnect()
                Catch
                End Try
            End If
        End Try
    End Function

    Private Function CreateZkemInstance() As CZKEM
        Try
            Return New CZKEM()
        Catch ex As Exception
            Throw New Exception("Không khởi tạo được COM zkemkeeper.ZKEM. Kiểm tra đăng ký DLL và kiến trúc x86.", ex)
        End Try
    End Function

    Private Function BuildMachineCandidates(primary As Integer) As Integer()
        Dim list As New List(Of Integer)
        If primary >= 0 Then list.Add(primary)
        list.Add(1)
        list.Add(0)
        Return list.Distinct().ToArray()
    End Function

    Private Function SafeParseInt(text As String, defaultValue As Integer) As Integer
        Dim n As Integer
        If Integer.TryParse(text, n) Then Return n
        Return defaultValue
    End Function

    Private Function RunOnStaThreadAsync(Of T)(work As Func(Of T)) As Task(Of T)
        Dim tcs As New TaskCompletionSource(Of T)()
        Dim th As New Thread(Sub()
                                 Try
                                     Dim result = work()
                                     tcs.SetResult(result)
                                 Catch ex As Exception
                                     tcs.SetException(ex)
                                 End Try
                             End Sub)
        th.SetApartmentState(ApartmentState.STA)
        th.IsBackground = True
        th.Start()
        Return tcs.Task
    End Function

    Private Sub InsertLogs(records As List(Of AttendanceRecord))
        Dim connectionString = LoadConnectionString()
        Using conn As New SqlConnection(connectionString)
            conn.Open()
            For Each r In records
                Using cmd As New SqlCommand("INSERT INTO LogATT (MCC, Maso_NV, DateTime, DateIn, Lan, TimeIn, Att_Name) VALUES (@MCC, @Maso_NV, @DateTime, @DateIn, @Lan, @TimeIn, @Att_Name)", conn)
                    cmd.Parameters.AddWithValue("@MCC", r.MCC)
                    cmd.Parameters.AddWithValue("@Maso_NV", r.MasoNV)
                    cmd.Parameters.AddWithValue("@DateTime", r.DateTimeVal)
                    cmd.Parameters.AddWithValue("@DateIn", r.DateIn)
                    cmd.Parameters.AddWithValue("@Lan", r.Lan)
                    cmd.Parameters.AddWithValue("@TimeIn", r.TimeIn)
                    cmd.Parameters.AddWithValue("@Att_Name", r.AttName)
                    cmd.ExecuteNonQuery()
                End Using
            Next
        End Using
    End Sub

    Private Sub DeleteLogs()
        Dim connectionString = LoadConnectionString()
        Using conn As New SqlConnection(connectionString)
            conn.Open()
            Using cmd As New SqlCommand("DELETE FROM LogATT", conn)
                cmd.ExecuteNonQuery()
            End Using
        End Using
    End Sub

    Private Function LoadConnectionString() As String
        ' Đọc file Config.ini đặt cùng thư mục với file .exe
        Dim exeDir = AppDomain.CurrentDomain.BaseDirectory
        Dim iniPath = Path.Combine(exeDir, "Config.ini")
        If Not File.Exists(iniPath) Then Throw New FileNotFoundException("Không tìm thấy Config.ini bên cạnh chương trình.", iniPath)

        Dim server As String = ""
        Dim user As String = ""
        Dim pwd As String = ""
        Dim db As String = ""

        Dim inSection As Boolean = False
        For Each line In File.ReadAllLines(iniPath)
            Dim trimmed = line.Trim()
            If trimmed.StartsWith(";") OrElse trimmed = "" Then Continue For
            If trimmed.StartsWith("[") AndAlso trimmed.EndsWith("]") Then
                inSection = String.Equals(trimmed, "[SQLServer_Connect]", StringComparison.OrdinalIgnoreCase)
                Continue For
            End If
            If Not inSection Then Continue For

            Dim parts = trimmed.Split({"="c}, 2, StringSplitOptions.None)
            If parts.Length <> 2 Then Continue For
            Dim key = parts(0).Trim().ToLowerInvariant()
            Dim value = parts(1).Trim()
            Select Case key
                Case "server" : server = value
                Case "user" : user = value
                Case "password" : pwd = value
                Case "database" : db = value
            End Select
        Next

        If String.IsNullOrWhiteSpace(server) OrElse String.IsNullOrWhiteSpace(db) Then
            Throw New InvalidDataException("Thiếu server hoặc database trong Config.ini.")
        End If

        Dim builder As New SqlConnectionStringBuilder() With {
            .DataSource = server,
            .InitialCatalog = db,
            .TrustServerCertificate = True
        }

        If Not String.IsNullOrWhiteSpace(user) Then
            builder.UserID = user
            builder.Password = pwd
            builder.IntegratedSecurity = False
        Else
            builder.IntegratedSecurity = True
        End If

        Return builder.ConnectionString
    End Function

    Private Class AttendanceRequest
        Public Property Type As String
        Public Property DeviceIp As String
        Public Property DevicePort As Integer
        Public Property Name As String
        Public Property FromDate As DateTime
        Public Property ToDate As DateTime
        Public Property Delete As Integer
    End Class

    Private Class AttendanceRecord
        Public Property MCC As Integer
        Public Property MasoNV As Integer
        Public Property DateTimeVal As DateTime
        Public Property DateIn As DateTime
        Public Property Lan As Integer
        Public Property TimeIn As TimeSpan
        Public Property AttName As String
    End Class
End Class

