// // EmailLogService.swift // oAI // // Service for managing email handler activity logs // // SPDX-License-Identifier: AGPL-3.0-or-later // Copyright (C) 2026 Rune Olsen // // This file is part of oAI. // // oAI is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License as // published by the Free Software Foundation, either version 3 of the // License, or (at your option) any later version. // // oAI is distributed in the hope that it will be useful, but WITHOUT // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY // or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General // Public License for more details. // // You should have received a copy of the GNU Affero General Public // License along with oAI. If not, see . import Foundation import os @Observable final class EmailLogService { static let shared = EmailLogService() private let db = DatabaseService.shared private let log = Logger(subsystem: "com.oai.oAI", category: "email-log") private init() {} // MARK: - Log Operations /// Save a successful email processing log func logSuccess( sender: String, subject: String, emailContent: String, aiResponse: String, tokens: Int?, cost: Double?, responseTime: TimeInterval?, modelId: String? ) { let entry = EmailLog( sender: sender, subject: subject, emailContent: emailContent, aiResponse: aiResponse, status: .success, tokens: tokens, cost: cost, responseTime: responseTime, modelId: modelId ) db.saveEmailLog(entry) log.info("Email log saved: \(sender) - \(subject) [SUCCESS]") } /// Save a failed email processing log func logError( sender: String, subject: String, emailContent: String, errorMessage: String, modelId: String? ) { let entry = EmailLog( sender: sender, subject: subject, emailContent: emailContent, aiResponse: nil, status: .error, errorMessage: errorMessage, modelId: modelId ) db.saveEmailLog(entry) log.error("Email log saved: \(sender) - \(subject) [ERROR: \(errorMessage)]") } /// Load recent email logs (default: 100 most recent) func loadLogs(limit: Int = 100) -> [EmailLog] { do { return try db.loadEmailLogs(limit: limit) } catch { log.error("Failed to load email logs: \(error.localizedDescription)") return [] } } /// Delete a specific log entry func deleteLog(id: UUID) { db.deleteEmailLog(id: id) log.info("Email log deleted: \(id.uuidString)") } /// Clear all email logs func clearAllLogs() { db.clearEmailLogs() log.info("All email logs cleared") } /// Get total count of email logs func getLogCount() -> Int { do { return try db.getEmailLogCount() } catch { log.error("Failed to get email log count: \(error.localizedDescription)") return 0 } } // MARK: - Statistics /// Get email processing statistics func getStatistics() -> EmailStatistics { let logs = loadLogs(limit: 1000) // Last 1000 for stats let total = logs.count let successful = logs.filter { $0.status == .success }.count let errors = logs.filter { $0.status == .error }.count let totalTokens = logs.compactMap { $0.tokens }.reduce(0, +) let totalCost = logs.compactMap { $0.cost }.reduce(0.0, +) let avgResponseTime: TimeInterval? let responseTimes = logs.compactMap { $0.responseTime } if !responseTimes.isEmpty { avgResponseTime = responseTimes.reduce(0, +) / Double(responseTimes.count) } else { avgResponseTime = nil } return EmailStatistics( total: total, successful: successful, errors: errors, totalTokens: totalTokens, totalCost: totalCost, averageResponseTime: avgResponseTime ) } } // MARK: - Statistics Model struct EmailStatistics { let total: Int let successful: Int let errors: Int let totalTokens: Int let totalCost: Double let averageResponseTime: TimeInterval? var successRate: Double { guard total > 0 else { return 0.0 } return Double(successful) / Double(total) } var errorRate: Double { guard total > 0 else { return 0.0 } return Double(errors) / Double(total) } }