import Foundation
import Logging

final class FileLogSink: @unchecked Sendable {
    static let shared = FileLogSink()
    static let defaultURL: URL = {
        let base = FileManager.default.urls(for: .libraryDirectory, in: .userDomainMask).first
            ?? FileManager.default.homeDirectoryForCurrentUser.appendingPathComponent("Library", isDirectory: true)
        return base
            .appendingPathComponent("Logs", isDirectory: true)
            .appendingPathComponent("CodexBar", isDirectory: true)
            .appendingPathComponent("CodexBar.log")
    }()

    private let queue = DispatchQueue(label: "com.steipete.codexbar.filelog", qos: .utility)
    private let fileManager: FileManager
    private var isEnabled = false
    private var fileHandle: FileHandle?
    private var fileURL: URL = FileLogSink.defaultURL
    private let maxBytes: Int64 = 10 * 1024 * 1024

    init(fileManager: FileManager = .default) {
        self.fileManager = fileManager
    }

    func setEnabled(_ enabled: Bool, fileURL: URL = FileLogSink.defaultURL) {
        self.queue.async {
            self.isEnabled = enabled
            self.fileURL = fileURL
            if !enabled {
                self.closeHandle()
                return
            }
            _ = self.openHandleIfNeeded()
        }
    }

    func currentURL() -> URL {
        self.queue.sync { self.fileURL }
    }

    func isWriteEnabled() -> Bool {
        self.queue.sync { self.isEnabled }
    }

    func write(_ text: String) {
        self.queue.async {
            guard self.isEnabled else { return }
            guard let data = text.data(using: .utf8) else { return }
            guard let handle = self.openHandleIfNeeded() else { return }
            handle.write(data)
        }
    }

    private func openHandleIfNeeded() -> FileHandle? {
        if let handle = self.fileHandle { return handle }
        do {
            try self.prepareFile(at: self.fileURL)
            let handle = try FileHandle(forWritingTo: self.fileURL)
            handle.seekToEndOfFile()
            self.fileHandle = handle
            return handle
        } catch {
            return nil
        }
    }

    private func prepareFile(at url: URL) throws {
        let directory = url.deletingLastPathComponent()
        if !self.fileManager.fileExists(atPath: directory.path) {
            try self.fileManager.createDirectory(at: directory, withIntermediateDirectories: true)
        }
        if self.fileManager.fileExists(atPath: url.path) {
            let attributes = try self.fileManager.attributesOfItem(atPath: url.path)
            let size = (attributes[.size] as? NSNumber)?.int64Value ?? 0
            if size > self.maxBytes {
                try self.fileManager.removeItem(at: url)
            }
        }
        if !self.fileManager.fileExists(atPath: url.path) {
            _ = self.fileManager.createFile(atPath: url.path, contents: nil)
        }
    }

    private func closeHandle() {
        if let handle = self.fileHandle {
            try? handle.close()
        }
        self.fileHandle = nil
    }
}

struct FileLogHandler: LogHandler {
    var metadata: Logger.Metadata = [:]
    var logLevel: Logger.Level = .info

    private let label: String
    private let sink: FileLogSink

    init(label: String, sink: FileLogSink = .shared) {
        self.label = label
        self.sink = sink
    }

    subscript(metadataKey metadataKey: String) -> Logger.Metadata.Value? {
        get { self.metadata[metadataKey] }
        set { self.metadata[metadataKey] = newValue }
    }

    func log(event: LogEvent) {
        guard self.sink.isWriteEnabled() else { return }
        let ts = Self.timestamp()
        var combined = self.metadata
        if let metadata = event.metadata { combined.merge(metadata, uniquingKeysWith: { _, new in new }) }
        var metaText = ""
        if !combined.isEmpty {
            let pairs = combined
                .sorted(by: { $0.key < $1.key })
                .map { key, value in
                    let rendered = Self.renderMetadataValue(value)
                    let safeValue = LogRedactor.redact(rendered)
                    return "\(key)=\(safeValue)"
                }
                .joined(separator: " ")
            metaText = " \(pairs)"
        }
        let safeMessage = LogRedactor.redact("\(event.message)")
        let lineText = "[\(ts)] [\(event.level.rawValue.uppercased())] \(self.label): \(safeMessage)\(metaText)\n"
        _ = event.source
        _ = event.file
        _ = event.function
        _ = event.line
        self.sink.write(lineText)
    }

    private static func timestamp() -> String {
        let formatter = ISO8601DateFormatter()
        formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
        return formatter.string(from: Date())
    }

    private static func renderMetadataValue(_ value: Logger.Metadata.Value) -> String {
        switch value {
        case let .string(text):
            text
        default:
            String(describing: value)
        }
    }
}
