//
//  LicenseManager.swift
//  CmdBar
//
//  Created by Igor Kolokolnikov on 8/26/23.
//

import AppKit
import CryptoKit

fileprivate struct LicenseModel: Codable {
    let valid: Bool
    let error: String
    let email: String
    let key:   String
}

// Converts string to a hash.
fileprivate func hashString(_ string: String) -> String {
    return SHA256.hash(data: Data(string.utf8)).compactMap { String(format: "%02x", $0) }.joined()
}

// Converts passphrase to a SymmetricKey.
fileprivate func passToKey(from passphrase: String) -> SymmetricKey {
    let passphraseData = Data(passphrase.utf8)
    let keyData = SHA256.hash(data: passphraseData).withUnsafeBytes { buffer in
        return Data(buffer)
    }
    return SymmetricKey(data: keyData)
}

final class LicenseManager {
    static let standard = LicenseManager()
    
    private let passkey = passToKey(from: "B2B67FB0E22B814CE5A6E7DA729A068BF21C9490919EA0B42FCEB322F9C9BA82")
    private let defaults = UserDefaults.standard
    private var timer: DispatchSourceTimer?
    private var quitAfterMinutes = 20.0 * 60.0 // quit after 20 minutes
    
    func isRegistered() -> Bool {
        guard let email = defaults.object(forKey: "Email") as? String,
              let key = defaults.object(forKey: "Key") as? String,
              let licenseData = defaults.object(forKey: "License") as? Data,
              let unsealedBox = try? AES.GCM.SealedBox(combined: licenseData),
              let licenseHashData = try? AES.GCM.open(unsealedBox, using: passkey),
              !email.isEmpty,
              !key.isEmpty
        else { return false }
        
        let currentHashData = Data(SHA256.hash(data: Data(hashString("\(email)\(key)\(NSUserName())").utf8))
            .compactMap { String(format: "%02x", $0) }.joined().utf8)
        
        return licenseHashData == currentHashData
    }
    
    func activateLicense(email: String, key: String, completionHandler: @escaping (_ data: Resulting<Bool, String>) -> Void) {
        guard let url = URL(string: EndpointConstants.validateURL)?.appending(component: key)
        else { completionHandler(.failure); return }
        
        URLSession.shared.dataTask(with: url) { [weak self] (data, response, error) in
            guard
                let data = data,
                error == nil,
                let response = response as? HTTPURLResponse,
                (200...299).contains(response.statusCode)
            else { completionHandler(.failure); return }
            
            guard let jsonResponse = try? JSONDecoder().decode(LicenseModel.self, from: data)
            else { completionHandler(.failure); return }
            
            if jsonResponse.email == email && jsonResponse.key == key {
                self?.saveLicense(with: email, and: key)
                self?.stopTimer()
                completionHandler(.success)
            } else {
                completionHandler(.failure)
            }
        }.resume()
    }
    
    func deactivateLicense() {
        clearLicense()
        startTimer()
    }
    
    func getLicense() -> (email: String, key: String) {
        guard
            let email = defaults.object(forKey: "Email") as? String,
            let key = defaults.object(forKey: "Key") as? String
        else { return ("", "") }
        return (email, key)
    }
    
    private func saveLicense(with email: String, and key: String) {
        let defaults = UserDefaults.standard
        
        let licenseHashData = Data(SHA256.hash(data: Data(hashString("\(email)\(key)\(NSUserName())").utf8))
            .compactMap { String(format: "%02x", $0) }.joined().utf8)
        
        guard let sealedBoxData = try? AES.GCM.seal(licenseHashData, using: passkey).combined else { return }
        defaults.set(email, forKey: "Email")
        defaults.set(key, forKey: "Key")
        defaults.set(sealedBoxData, forKey: "License")
    }
    
    func clearLicense() {
        defaults.set(nil, forKey: "Email")
        defaults.set(nil, forKey: "Key")
        defaults.set(nil, forKey: "License")
    }
    
    // MARK: - Timer
    func startTimer() {
        timer = DispatchSource.makeTimerSource(queue: DispatchQueue.global())
        timer?.schedule(
            deadline: .now() + quitAfterMinutes,
            repeating: DispatchTimeInterval.never
        )
        timer?.setEventHandler {
            DispatchQueue.main.async { NSApplication.shared.terminate(self) }
        }
        timer?.resume()
    }
    
    func stopTimer() {
        timer?.cancel()
        timer = nil
    }
}
