import Foundation
import Observation
import UIKit

/// Manages avatar image storage on disk with in-memory cache.
///
/// Android parity: `AvatarFileManager.kt` encrypts avatars with password "rosetta-a".
/// Desktop parity: `AvatarProvider.tsx` uses `AVATAR_PASSWORD_TO_ENCODE = "rosetta-a"`.
///
/// Storage: `Application Support/Rosetta/Avatars/{normalizedKey}.enc`
@Observable
@MainActor
final class AvatarRepository {

    static let shared = AvatarRepository()
    private init() {}

    /// Android/Desktop parity: fixed password for avatar encryption at rest.
    private static let avatarPassword = "rosetta-a"

    /// Incremented on every avatar save/remove — views that read this property
    /// will re-render and pick up the latest avatar from cache.
    private(set) var avatarVersion: UInt = 0

    /// In-memory cache for decoded UIImages — keyed by normalized public key.
    /// Bounded: 50MB / 200 images max to prevent memory pressure on low-end devices.
    private let cache: NSCache<NSString, UIImage> = {
        let c = NSCache<NSString, UIImage>()
        c.totalCostLimit = 50 * 1024 * 1024  // 50MB
        c.countLimit = 200
        return c
    }()

    private let compressionQuality: CGFloat = 0.8

    // MARK: - Public API

    /// Saves an avatar image for the given public key.
    /// Android parity: encrypts with "rosetta-a" password before writing to disk.
    func saveAvatar(publicKey: String, image: UIImage) {
        let key = normalizedKey(publicKey)
        guard let jpegData = image.jpegData(compressionQuality: compressionQuality) else { return }
        let url = avatarURL(for: key)
        ensureDirectoryExists()

        // Encrypt with "rosetta-a" (Android/Desktop parity)
        if let encrypted = try? CryptoManager.shared.encryptWithPassword(jpegData, password: Self.avatarPassword),
           let encData = encrypted.data(using: .utf8) {
            try? encData.write(to: url, options: [.atomic, .completeFileProtectionUntilFirstUserAuthentication])
        } else {
            // Fallback: plaintext (should never happen)
            try? jpegData.write(to: url, options: [.atomic, .completeFileProtectionUntilFirstUserAuthentication])
        }
        cache.setObject(image, forKey: key as NSString, cost: jpegData.count)
        avatarVersion += 1
    }

    /// Saves an avatar from a base64-encoded image string (used when receiving from network).
    func saveAvatarFromBase64(_ base64: String, publicKey: String) {
        let rawBase64: String
        if base64.hasPrefix("data:") {
            rawBase64 = String(base64.drop(while: { $0 != "," }).dropFirst())
        } else {
            rawBase64 = base64
        }
        guard let data = Data(base64Encoded: rawBase64),
              let image = UIImage(data: data) else { return }
        saveAvatar(publicKey: publicKey, image: image)
    }

    /// Loads avatar for the given public key.
    func loadAvatar(publicKey: String) -> UIImage? {
        if let systemAvatar = systemAccountAvatar(for: publicKey) {
            return systemAvatar
        }
        let key = normalizedKey(publicKey)
        if let cached = cache.object(forKey: key as NSString) {
            return cached
        }
        let url = avatarURL(for: key)
        guard let fileData = try? Data(contentsOf: url) else { return nil }

        // Try decrypting (new encrypted format)
        if let encryptedString = String(data: fileData, encoding: .utf8),
           let decrypted = try? CryptoManager.shared.decryptWithPassword(encryptedString, password: Self.avatarPassword),
           let image = UIImage(data: decrypted) {
            cache.setObject(image, forKey: key as NSString, cost: decrypted.count)
            return image
        }

        // Fallback: try as plaintext JPEG (legacy unencrypted files)
        if let image = UIImage(data: fileData) {
            cache.setObject(image, forKey: key as NSString, cost: fileData.count)
            // Re-save encrypted for next time
            saveAvatar(publicKey: publicKey, image: image)
            return image
        }

        return nil
    }

    private func systemAccountAvatar(for publicKey: String) -> UIImage? {
        if publicKey == SystemAccounts.safePublicKey {
            return UIImage(named: "safe-avatar")
        }
        if publicKey == SystemAccounts.updatesPublicKey {
            return UIImage(named: "updates-avatar")
        }
        return nil
    }

    func loadAvatarBase64(publicKey: String) -> String? {
        guard let image = loadAvatar(publicKey: publicKey),
              let data = image.jpegData(compressionQuality: compressionQuality) else {
            return nil
        }
        return data.base64EncodedString()
    }

    func removeAvatar(publicKey: String) {
        let key = normalizedKey(publicKey)
        cache.removeObject(forKey: key as NSString)
        let url = avatarURL(for: key)
        try? FileManager.default.removeItem(at: url)
        avatarVersion += 1
    }

    func clearCache() {
        cache.removeAllObjects()
    }

    func clearAll() {
        cache.removeAllObjects()
        if let directory = avatarsDirectory {
            try? FileManager.default.removeItem(at: directory)
        }
        avatarVersion += 1
    }

    // MARK: - Private

    private var avatarsDirectory: URL? {
        FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)
            .first?
            .appendingPathComponent("Rosetta/Avatars", isDirectory: true)
    }

    private func avatarURL(for normalizedKey: String) -> URL {
        avatarsDirectory!
            .appendingPathComponent("\(normalizedKey).enc")
    }

    private func normalizedKey(_ publicKey: String) -> String {
        publicKey
            .replacingOccurrences(of: "0x", with: "")
            .lowercased()
    }

    private func ensureDirectoryExists() {
        guard let directory = avatarsDirectory else { return }
        if !FileManager.default.fileExists(atPath: directory.path) {
            try? FileManager.default.createDirectory(
                at: directory,
                withIntermediateDirectories: true
            )
        }
    }
}
