/*
RebreakURLFilterControlProvider — die NEURLFilter-Control-Provider-Extension.

Adaptiert aus Apples `SimpleURLFilter`-Sample (`URLFilterControlProvider.swift`).
Unterschied: `SwiftBloomFilter` ist nicht als Package importiert, sondern als
`BloomFilter.swift`/`Murmur3Hash.swift`/`FNV1aHash.swift` direkt in dieses
Extension-Target vendored — daher kein `import`.

Aufgabe: dem System via `fetchPrefilter()` den lokalen Bloom-Vorfilter liefern.
Das System prüft jede URL zuerst gegen diesen Bloom-Filter; nur bei einem
Treffer folgt ein PIR-Lookup gegen den Server.
*/

import Foundation
import NetworkExtension
import OSLog

enum ProviderFilterError: Error {
    case loadError(message: String?)
}

/// Schreibt in den geteilten App-Group-Log-Store (`url_filter_logs`) — den die
/// Haupt-App via `getProtectionLogs()` / `activateUrlFilter` ausliest. Die
/// Extension läuft als eigener Prozess; das ist unser einziges Fenster rein,
/// um zu sehen ob sie überhaupt geladen wird und was `fetchPrefilter` tut.
enum ExtLog {
    static func write(_ msg: String) {
        NSLog("REBREAK_EXT %@", msg)
        guard let d = UserDefaults(suiteName: "group.org.rebreak.app") else { return }
        let line = "[EXT \(ISO8601DateFormatter().string(from: Date()))] \(msg)"
        var logs = d.stringArray(forKey: "url_filter_logs") ?? []
        logs.append(line)
        if logs.count > 200 { logs.removeFirst(logs.count - 200) }
        d.set(logs, forKey: "url_filter_logs")
    }
}

@main
class RebreakURLFilterControlProvider {

    /// Name der Bloom-Filter-Bundle-Resource (`bloom_filter.plist`).
    let filterPlistFileName = "bloom_filter"
    var filter: BloomFilter?

    private let log = Logger(subsystem: "org.rebreak.app.urlfilter", category: "controlprovider")

    required init() {
        ExtLog.write("controlProvider init — Extension-Prozess gestartet")
    }

    func loadBloomFilter() throws {
        guard let filterFile = Bundle.main.url(forResource: filterPlistFileName, withExtension: "plist") else {
            throw ProviderFilterError.loadError(
                message: "Bloom-Filter-Plist '\(filterPlistFileName).plist' nicht im Bundle gefunden.")
        }
        let data = try Data(contentsOf: filterFile)
        filter = try PropertyListDecoder().decode(BloomFilter.self, from: data)
    }

    /// Tag = Identität des aktuellen Filters. Ändert er sich nicht, kann ein
    /// erneuter (teurer) Fetch übersprungen werden.
    func filterTag() -> String? {
        guard let filter else { return nil }
        return filter.hashValue.description
    }
}

extension RebreakURLFilterControlProvider: NEURLFilterControlProvider {

    func start() async throws {
        ExtLog.write("start()")
        log.log("RebreakURLFilterControlProvider — start")
    }

    func stop(reason: NEProviderStopReason) async throws {
        ExtLog.write("stop() reason=\(reason.rawValue)")
        log.log("RebreakURLFilterControlProvider — stop (reason \(reason.rawValue))")
    }

    /// Vom System aufgerufen, um den Bloom-Vorfilter zu holen. Bei unverändertem
    /// `existingPrefilterTag` → `nil` (spart den Fetch). Ein geworfener Fehler
    /// scheitert die Filter-Installation.
    func fetchPrefilter(existingPrefilterTag: String?) async throws -> NEURLFilterPrefilter? {
        ExtLog.write("fetchPrefilter ENTER (existingTag=\(existingPrefilterTag ?? "nil"))")
        log.log("fetchPrefilter — existingTag: \(existingPrefilterTag ?? "(nil)")")

        // Unverändert seit letztem Fetch → nichts zu tun.
        guard existingPrefilterTag == nil || existingPrefilterTag != filterTag() else {
            ExtLog.write("fetchPrefilter → nil (Prefilter unverändert)")
            log.debug("Prefilter unverändert (tag: '\(existingPrefilterTag ?? "")').")
            return nil
        }

        if filter == nil {
            do {
                try loadBloomFilter()
                ExtLog.write("bloom_filter.plist geladen + dekodiert")
            } catch {
                ExtLog.write("❌ loadBloomFilter FEHLER: \(error) → fetchPrefilter → nil")
                log.error("Bloom-Filter konnte nicht geladen werden: \(error)")
                return nil
            }
        }

        guard let filter, let filterData = filter.data, let tag = filterTag() else {
            ExtLog.write("❌ filter/filterData/tag unerwartet nil → fetchPrefilter → nil")
            log.error("Bloom-Filter unerwartet nil.")
            return nil
        }

        log.debug("Bloom-Filter: \(filter.description)")

        // Daten in eine temporäre Datei schreiben statt in-memory zu übergeben
        // (Apple-Empfehlung — vermeidet Memory-Druck bei großen Datensätzen).
        let fileURL = FileManager.default.temporaryDirectory.appendingPathComponent("bloomfilterdata")
        do {
            try filterData.write(to: fileURL)
        } catch {
            ExtLog.write("❌ filterData.write FEHLER: \(error) → fetchPrefilter → nil")
            log.error("Bloom-Bit-Vektor konnte nicht in Temp-Datei geschrieben werden: \(error)")
            return nil
        }

        let prefilter = NEURLFilterPrefilter(
            data: .temporaryFilepath(fileURL),
            tag: tag,
            bitCount: Int(filter.bitCount),
            hashCount: Int(filter.hashCount),
            murmurSeed: filter.murmurSeed
        )
        ExtLog.write("✅ fetchPrefilter → Prefilter (tag=\(tag) bitCount=\(filter.bitCount) hashCount=\(filter.hashCount) bytes=\(filterData.count))")
        log.debug("Prefilter geliefert (tag '\(tag)').")
        return prefilter
    }
}
