// LoopFollow
// LogManager.swift

import Foundation
import UIKit

class LogManager {
    static let shared = LogManager()

    private let fileManager = FileManager.default
    private let logDirectory: URL
    private let dateFormatter: DateFormatter
    private let consoleQueue = DispatchQueue(label: "com.loopfollow.log.console", qos: .background)

    private let rateLimitQueue = DispatchQueue(label: "com.loopfollow.log.ratelimit")
    private var lastLoggedTimestamps: [String: Date] = [:]

    private var shouldLogVersionHeader: Bool = true

    enum Category: String, CaseIterable {
        case bluetooth = "Bluetooth"
        case nightscout = "Nightscout"
        case apns = "APNS"
        case general = "General"
        case contact = "Contact"
        case taskScheduler = "Task Scheduler"
        case dexcom = "Dexcom"
        case alarm = "Alarm"
        case volumeButtonSnooze = "Volume Button Snooze"
        case calendar = "Calendar"
        case deviceStatus = "Device Status"
        case remote = "Remote"
        case telemetry = "Telemetry"
    }

    init() {
        let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!
        logDirectory = documentsDirectory.appendingPathComponent("Logs")
        if !fileManager.fileExists(atPath: logDirectory.path) {
            try? fileManager.createDirectory(at: logDirectory, withIntermediateDirectories: true, attributes: nil)
        }
        dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "yyyy-MM-dd"
    }

    private func formattedLogMessage(for category: Category, message: String) -> String {
        let timestamp = DateFormatter.localizedString(from: Date(), dateStyle: .none, timeStyle: .medium)
        return "[\(timestamp)] [\(category.rawValue)] \(message)"
    }

    /// Logs a message with an optional rate limit.
    ///
    /// - Parameters:
    ///   - category: The log category.
    ///   - message: The message to log.
    ///   - isDebug: Indicates if this is a debug log.
    ///   - limitIdentifier: Optional key to rate-limit similar log messages.
    ///   - limitInterval: Time interval (in seconds) to wait before logging the same type again.
    func log(category: Category, message: String, isDebug: Bool = false, limitIdentifier: String? = nil, limitInterval: TimeInterval = 300) {
        let safeMessage = LogRedactor.sweep(message)
        let logMessage = formattedLogMessage(for: category, message: safeMessage)

        consoleQueue.async {
            print(logMessage)
        }

        if category == .taskScheduler && isDebug {
            return
        }

        if let key = limitIdentifier, !Storage.shared.debugLogLevel.value {
            let shouldLog: Bool = rateLimitQueue.sync {
                if let lastLogged = lastLoggedTimestamps[key] {
                    let interval = Date().timeIntervalSince(lastLogged)
                    if interval < limitInterval {
                        return false
                    }
                }
                lastLoggedTimestamps[key] = Date()
                return true
            }
            if !shouldLog {
                return
            }
        }

        if !isDebug || Storage.shared.debugLogLevel.value {
            let logFileURL = currentLogFileURL
            writeVersionHeaderIfNeeded(for: logFileURL)
            append(logMessage + "\n", to: logFileURL)
        }
    }

    /// Helper method: checks if the log file is empty.
    private func isLogFileEmpty(at fileURL: URL) -> Bool {
        if !fileManager.fileExists(atPath: fileURL.path) { return true }
        if let attributes = try? fileManager.attributesOfItem(atPath: fileURL.path),
           let fileSize = attributes[.size] as? UInt64
        {
            return fileSize == 0
        }
        return false
    }

    /// Helper method: writes the version header if needed.
    private func writeVersionHeaderIfNeeded(for fileURL: URL) {
        if shouldLogVersionHeader || isLogFileEmpty(at: fileURL) {
            let versionManager = AppVersionManager()
            let version = versionManager.version()

            // Retrieve build details
            let buildDetails = BuildDetails.default
            let formattedBuildDate = dateTimeUtils.formattedDate(from: buildDetails.buildDate())
            let branchAndSha = buildDetails.branchAndSha
            let expiration = dateTimeUtils.formattedDate(from: buildDetails.calculateExpirationDate())
            let expirationHeaderString = buildDetails.expirationHeaderString
            let isMacApp = buildDetails.isMacApp()
            let isSimulatorBuild = buildDetails.isSimulatorBuild()
            let osLabel: String
            let osVersion: String
            if isMacApp {
                osLabel = "macOS"
                let v = ProcessInfo.processInfo.operatingSystemVersion
                osVersion = "\(v.majorVersion).\(v.minorVersion).\(v.patchVersion)"
            } else {
                osLabel = "iOS"
                osVersion = UIDevice.current.systemVersion
            }

            // Assemble header information
            var headerLines = [String]()
            headerLines.append("LoopFollow Version: \(version)")
            if !isMacApp, !isSimulatorBuild {
                headerLines.append("\(expirationHeaderString): \(expiration)")
            }
            headerLines.append("Built: \(formattedBuildDate)")
            headerLines.append("Branch: \(branchAndSha)")
            headerLines.append("\(osLabel): \(osVersion)")

            let headerMessage = headerLines.joined(separator: ", ") + "\n"
            let logMessage = formattedLogMessage(for: .general, message: headerMessage)

            append(logMessage, to: fileURL)
            shouldLogVersionHeader = false
        }
    }

    func cleanupOldLogs() {
        let today = dateFormatter.string(from: Date())
        let yesterday = dateFormatter.string(from: Calendar.current.date(byAdding: .day, value: -1, to: Date())!)
        do {
            let logFiles = try fileManager.contentsOfDirectory(at: logDirectory, includingPropertiesForKeys: nil)
            for logFile in logFiles {
                let filename = logFile.lastPathComponent
                if !filename.contains(today), !filename.contains(yesterday) {
                    try fileManager.removeItem(at: logFile)
                }
            }
        } catch {
            print("Failed to clean up old logs: \(error)")
        }
    }

    func logFileURL(for date: Date) -> URL {
        let dateString = dateFormatter.string(from: date)
        return logDirectory.appendingPathComponent("LoopFollow \(dateString).log")
    }

    func logFilesForTodayAndYesterday() -> [URL] {
        let today = logFileURL(for: Date())
        let yesterday = logFileURL(for: Calendar.current.date(byAdding: .day, value: -1, to: Date())!)
        return [today, yesterday].filter { fileManager.fileExists(atPath: $0.path) }
    }

    var currentLogFileURL: URL {
        return logFileURL(for: Date())
    }

    private func append(_ message: String, to fileURL: URL) {
        if !fileManager.fileExists(atPath: fileURL.path) {
            fileManager.createFile(atPath: fileURL.path, contents: nil, attributes: nil)
        }
        if let fileHandle = try? FileHandle(forWritingTo: fileURL) {
            defer { fileHandle.closeFile() }
            fileHandle.seekToEndOfFile()
            if let data = message.data(using: .utf8) {
                fileHandle.write(data)
            }
        } else {
            print("Failed to open log file at \(fileURL.path)")
        }
    }
}
