import Foundation

/// Encrypted local message cache.
/// Matches Python: chat_core.py message cache (message_cache/{conv_id}.json)
enum MessageCache {

    /// Save messages for a conversation (encrypted with local storage key)
    static func save(email: String, convId: String, messages: [[String: Any]], cacheKey: Data?) throws {
        let dir = try KeyStorage.getKeyDir(email: email).appendingPathComponent("message_cache")
        try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)

        let jsonData = try JSONSerialization.data(withJSONObject: messages)

        guard let cacheKey = cacheKey else {
            return  // Refuse to save plaintext message cache
        }
        let dataToWrite = try CryptoUtils.encryptLocal(jsonData, key: cacheKey)
        try dataToWrite.write(to: dir.appendingPathComponent("\(convId).json"), options: .completeFileProtection)
    }

    /// Load messages for a conversation
    static func load(email: String, convId: String, cacheKey: Data?) -> [[String: Any]]? {
        guard let dir = try? KeyStorage.getKeyDir(email: email) else { return nil }
        let path = dir.appendingPathComponent("message_cache").appendingPathComponent("\(convId).json")
        guard let raw = try? Data(contentsOf: path) else { return nil }

        let jsonData: Data
        if let cacheKey = cacheKey {
            if let decrypted = try? CryptoUtils.decryptLocal(raw, key: cacheKey) {
                jsonData = decrypted
            } else if let parsed = try? JSONSerialization.jsonObject(with: raw) as? [[String: Any]] {
                // Migration: re-encrypt plaintext cache and return
                try? save(email: email, convId: convId, messages: parsed, cacheKey: cacheKey)
                return parsed
            } else {
                // Corrupted — delete stale cache
                try? FileManager.default.removeItem(at: path)
                return nil
            }
        } else {
            jsonData = raw
        }

        return try? JSONSerialization.jsonObject(with: jsonData) as? [[String: Any]]
    }

    /// Search messages in a conversation
    static func search(email: String, convId: String, query: String, cacheKey: Data?) -> [[String: Any]] {
        guard let messages = load(email: email, convId: convId, cacheKey: cacheKey) else {
            return []
        }
        let lowerQuery = query.lowercased()
        return messages.filter { msg in
            if let text = msg["text"] as? String, text.lowercased().contains(lowerQuery) {
                return true
            }
            return false
        }
    }

    /// Delete cache for a conversation
    static func delete(email: String, convId: String) {
        guard let dir = try? KeyStorage.getKeyDir(email: email) else { return }
        let path = dir.appendingPathComponent("message_cache").appendingPathComponent("\(convId).json")
        try? FileManager.default.removeItem(at: path)
    }

    // MARK: - Per-Message Cache (for Double Ratchet - messages can only be decrypted once)

    /// Cache a decrypted message by its ID
    static func cacheDecryptedMessage(email: String, convId: String, messageId: String, plaintext: Data, cacheKey: Data?) {
        guard let dir = try? KeyStorage.getKeyDir(email: email) else { return }
        let cacheDir = dir.appendingPathComponent("decrypted_messages").appendingPathComponent(convId)
        try? FileManager.default.createDirectory(at: cacheDir, withIntermediateDirectories: true)

        let path = cacheDir.appendingPathComponent("\(messageId).bin")
        do {
            guard let cacheKey = cacheKey else { return }  // Refuse plaintext
            let dataToWrite = try CryptoUtils.encryptLocal(plaintext, key: cacheKey)
            try dataToWrite.write(to: path, options: .completeFileProtection)
        } catch {
            #if DEBUG
            print("DEBUG MessageCache: failed to cache message \(messageId): \(error)")
            #endif
        }
    }

    /// Load all cached decrypted messages for a conversation.
    /// Returns array of (messageId, plaintext) tuples.
    static func loadAllCachedMessages(email: String, convId: String, cacheKey: Data?) -> [(String, Data)] {
        guard let dir = try? KeyStorage.getKeyDir(email: email) else { return [] }
        let cacheDir = dir.appendingPathComponent("decrypted_messages").appendingPathComponent(convId)
        guard let files = try? FileManager.default.contentsOfDirectory(at: cacheDir, includingPropertiesForKeys: nil) else { return [] }

        var result: [(String, Data)] = []
        for file in files where file.pathExtension == "bin" {
            let messageId = file.deletingPathExtension().lastPathComponent
            guard let raw = try? Data(contentsOf: file) else { continue }
            if let cacheKey = cacheKey,
               let decrypted = try? CryptoUtils.decryptLocal(raw, key: cacheKey) {
                result.append((messageId, decrypted))
            } else if cacheKey == nil {
                result.append((messageId, raw))
            }
        }
        return result
    }

    /// Get a cached decrypted message by ID
    static func getCachedMessage(email: String, convId: String, messageId: String, cacheKey: Data?) -> Data? {
        guard let dir = try? KeyStorage.getKeyDir(email: email) else { return nil }
        let path = dir.appendingPathComponent("decrypted_messages").appendingPathComponent(convId).appendingPathComponent("\(messageId).bin")
        guard let raw = try? Data(contentsOf: path) else { return nil }

        if let cacheKey = cacheKey {
            if let decrypted = try? CryptoUtils.decryptLocal(raw, key: cacheKey) {
                return decrypted
            }
            // Migration: try as plaintext, re-encrypt
            if let _ = try? JSONSerialization.jsonObject(with: raw) {
                cacheDecryptedMessage(email: email, convId: convId, messageId: messageId, plaintext: raw, cacheKey: cacheKey)
                return raw
            }
            // Corrupted — delete
            try? FileManager.default.removeItem(at: path)
            return nil
        }
        return raw
    }

    // MARK: - Conversation List Cache

    /// Save conversation list to disk (encrypted with local key)
    static func saveConversations(email: String, conversations: [Conversation], cacheKey: Data?) {
        guard let cacheKey = cacheKey else { return }
        guard let dir = try? KeyStorage.getKeyDir(email: email) else { return }
        do {
            let jsonData = try JSONEncoder().encode(conversations)
            let encrypted = try CryptoUtils.encryptLocal(jsonData, key: cacheKey)
            try encrypted.write(to: dir.appendingPathComponent("conversation_cache.json"), options: .completeFileProtection)
        } catch {
            #if DEBUG
            print("DEBUG MessageCache: failed to save conversations: \(error)")
            #endif
        }
    }

    /// Load conversation list from disk
    static func loadConversations(email: String, cacheKey: Data?) -> [Conversation]? {
        guard let cacheKey = cacheKey else { return nil }
        guard let dir = try? KeyStorage.getKeyDir(email: email) else { return nil }
        let path = dir.appendingPathComponent("conversation_cache.json")
        guard let raw = try? Data(contentsOf: path) else { return nil }
        guard let decrypted = try? CryptoUtils.decryptLocal(raw, key: cacheKey) else { return nil }
        return try? JSONDecoder().decode([Conversation].self, from: decrypted)
    }

    // MARK: - Avatar Disk Cache

    /// Save avatar data to disk (encrypted with local key)
    static func saveAvatar(email: String, key: String, data: Data, cacheKey: Data?) {
        guard let cacheKey = cacheKey else { return }
        guard let dir = try? KeyStorage.getKeyDir(email: email) else { return }
        let cacheDir = dir.appendingPathComponent("avatar_cache")
        try? FileManager.default.createDirectory(at: cacheDir, withIntermediateDirectories: true)
        do {
            let encrypted = try CryptoUtils.encryptLocal(data, key: cacheKey)
            try encrypted.write(to: cacheDir.appendingPathComponent("\(key).dat"), options: .completeFileProtection)
        } catch {
            #if DEBUG
            print("DEBUG MessageCache: failed to save avatar \(key): \(error)")
            #endif
        }
    }

    /// Load avatar data from disk
    static func loadAvatar(email: String, key: String, cacheKey: Data?) -> Data? {
        guard let cacheKey = cacheKey else { return nil }
        guard let dir = try? KeyStorage.getKeyDir(email: email) else { return nil }
        let path = dir.appendingPathComponent("avatar_cache").appendingPathComponent("\(key).dat")
        guard let raw = try? Data(contentsOf: path) else { return nil }
        return try? CryptoUtils.decryptLocal(raw, key: cacheKey)
    }

    /// Load all cached avatars from disk
    static func loadAllAvatars(email: String, cacheKey: Data?) -> [String: Data] {
        guard let cacheKey = cacheKey else { return [:] }
        guard let dir = try? KeyStorage.getKeyDir(email: email) else { return [:] }
        let cacheDir = dir.appendingPathComponent("avatar_cache")
        guard let files = try? FileManager.default.contentsOfDirectory(at: cacheDir, includingPropertiesForKeys: nil) else { return [:] }
        var result: [String: Data] = [:]
        for file in files where file.pathExtension == "dat" {
            let key = file.deletingPathExtension().lastPathComponent
            guard let raw = try? Data(contentsOf: file),
                  let decrypted = try? CryptoUtils.decryptLocal(raw, key: cacheKey) else { continue }
            result[key] = decrypted
        }
        return result
    }
}
