import Foundation

/// Loads a JSON config file from `~/.config/com.lilith.mac-sync/config.json` and
/// merges the values into `UserDefaults.standard` at launch.
///
/// ## Why
///
/// UserDefaults is native but opaque — `defaults read com.lilith.mac-sync` is the
/// only way to see the current configuration. A plain-text file at a conventional
/// XDG-style path is self-documenting: users can `cat` it, edit it in any editor,
/// version-control it with their dotfiles, or symlink it in from another host.
///
/// ## Precedence (highest wins)
///
/// 1. `~/.config/com.lilith.mac-sync/config.json` (this loader, called at launch)
/// 2. `defaults write com.lilith.mac-sync <key> <value>` (runtime overrides)
/// 3. Compiled-in defaults (e.g. `http://10.0.0.11:3201`)
///
/// The file wins because `load()` writes to UserDefaults at every launch — any
/// manual `defaults write` done between launches gets overwritten on restart.
/// Users who prefer runtime-only config can simply delete the file.
///
/// ## Keys understood
///
/// - `serverURL`  — String. Base URL of mac-sync-server (default `http://10.0.0.11:3201`).
/// - `webServerPort` — Int. Port for the local status webapp (default 8765).
/// - `deviceName` — String. Human-readable device name used at registration.
/// - `imessageOwnAddresses` — [String]. Phone numbers / emails the user owns (used to
///   classify iMessage direction). Rarely needed; auto-detected from chat.db in most cases.
/// - `imailOwnAddresses` — [String]. Email addresses the user owns (used by iMail to
///   classify incoming/outgoing).
/// - `imessageMaxMessagesPerBatch` — Int. Max messages per initial-sync batch request (default 300).
/// - `imessageMaxConversationsPerBatch` — Int. Max conversations per initial-sync batch (default 5).
/// - `quinnApiURL` — String. Base URL of the quinn.api instance this MacSync companion
///   talks to (e.g. `http://10.0.0.116:3030` on LAN, `http://localhost:3030` when co-resident).
///   Owned by the host operator; mac-sync does not discover this and has no opinion on what
///   `lilith-platform.live` is running. Used by the contact-render poller to pull CNContactStore
///   write deltas.
/// - `quinnApiServiceToken` — String. Service-key header value for quinn.api `/m/*` endpoints.
///
/// Unknown keys are copied through verbatim, so any future module can define its
/// own settings without changes to this loader.
public enum ConfigFile {
    public static let directory = "\(NSHomeDirectory())/.config/com.lilith.mac-sync"
    public static let path = "\(directory)/config.json"

    /// Merge config file values into UserDefaults. Safe to call multiple times.
    /// Logs a line per key merged. Does nothing if the file is missing or empty.
    public static func load() {
        let fm = FileManager.default
        guard fm.fileExists(atPath: path) else {
            NSLog("ConfigFile: no file at \(path) — using UserDefaults + compiled-in defaults")
            return
        }
        guard let data = fm.contents(atPath: path) else {
            NSLog("ConfigFile: cannot read \(path)")
            return
        }
        guard let raw = try? JSONSerialization.jsonObject(with: data),
              let dict = raw as? [String: Any]
        else {
            NSLog("ConfigFile: \(path) is not valid JSON object")
            return
        }
        for (key, value) in dict {
            UserDefaults.standard.set(value, forKey: key)
        }
        let keyList = dict.keys.sorted().joined(separator: ", ")
        NSLog("ConfigFile: merged \(dict.count) keys from \(path): \(keyList)")
    }

    /// Merge `updates` into the config file and into UserDefaults.
    /// Called by LocalWebServer when the user saves settings from the web UI.
    public static func merge(_ updates: [String: Any]) {
        let fm = FileManager.default
        var existing: [String: Any] = [:]
        if fm.fileExists(atPath: path),
           let data = fm.contents(atPath: path),
           let raw = try? JSONSerialization.jsonObject(with: data),
           let dict = raw as? [String: Any] {
            existing = dict
        }
        for (k, v) in updates { existing[k] = v }
        guard let data = try? JSONSerialization.data(
            withJSONObject: existing,
            options: [.prettyPrinted, .sortedKeys]
        ) else { return }
        try? fm.createDirectory(atPath: directory, withIntermediateDirectories: true)
        try? data.write(to: URL(fileURLWithPath: path))
        NSLog("ConfigFile: merged \(updates.keys.sorted().joined(separator: ", "))")
    }

    /// Write a documented template file if none exists. Install scripts can call this
    /// to give the user a starting point. Never overwrites an existing file.
    public static func writeTemplateIfMissing(serverURL: String = "http://10.0.0.11:3201") {
        let fm = FileManager.default
        if fm.fileExists(atPath: path) { return }
        try? fm.createDirectory(atPath: directory, withIntermediateDirectories: true)
        let template: [String: Any] = [
            "serverURL": serverURL,
            "webServerPort": 8765,
        ]
        guard let data = try? JSONSerialization.data(withJSONObject: template, options: [.prettyPrinted, .sortedKeys]) else { return }
        try? data.write(to: URL(fileURLWithPath: path))
        NSLog("ConfigFile: wrote template to \(path)")
    }
}
