//
// TestHelpers.swift
// bitchatTests
//
// This is free and unencumbered software released into the public domain.
// For more information, see <https://unlicense.org>
//

import Foundation
import CryptoKit
@testable import BitFoundation

// Kept local until the test-helper module is split out.
final class TestHelpers {
    
    // MARK: - Key Generation
    
    static func generateTestKeyPair() -> (privateKey: Curve25519.KeyAgreement.PrivateKey, publicKey: Curve25519.KeyAgreement.PublicKey) {
        let privateKey = Curve25519.KeyAgreement.PrivateKey()
        let publicKey = privateKey.publicKey
        return (privateKey, publicKey)
    }
    
    static func generateTestIdentity(peerID: String, nickname: String) -> (peerID: String, nickname: String, privateKey: Curve25519.KeyAgreement.PrivateKey, publicKey: Curve25519.KeyAgreement.PublicKey) {
        let (privateKey, publicKey) = generateTestKeyPair()
        return (peerID: peerID, nickname: nickname, privateKey: privateKey, publicKey: publicKey)
    }
    
    // MARK: - Message Creation
    
    static func createTestMessage(
        content: String = TestConstants.testMessage1,
        sender: String = TestConstants.testNickname1,
        senderPeerID: PeerID = PeerID(str: UUID().uuidString),
        isPrivate: Bool = false,
        recipientNickname: String? = nil,
        mentions: [String]? = nil
    ) -> BitchatMessage {
        return BitchatMessage(
            id: UUID().uuidString,
            sender: sender,
            content: content,
            timestamp: Date(),
            isRelay: false,
            originalSender: nil,
            isPrivate: isPrivate,
            recipientNickname: recipientNickname,
            senderPeerID: senderPeerID,
            mentions: mentions
        )
    }
    
    static func createTestPacket(
        type: UInt8 = 0x01,
        senderID: PeerID = PeerID(str: UUID().uuidString),
        recipientID: PeerID? = nil,
        payload: Data = Data("test payload".utf8),
        signature: Data? = nil,
        ttl: UInt8 = 3
    ) -> BitchatPacket {
        return BitchatPacket(
            type: type,
            senderID: Data(senderID.id.utf8),
            recipientID: recipientID?.id.data(using: .utf8),
            timestamp: UInt64(Date().timeIntervalSince1970 * 1000),
            payload: payload,
            signature: signature,
            ttl: ttl
        )
    }
    
    // MARK: - Data Generation
    
    static func generateRandomData(length: Int) -> Data {
        var data = Data(count: length)
        _ = data.withUnsafeMutableBytes { bytes in
            SecRandomCopyBytes(kSecRandomDefault, length, bytes.baseAddress!)
        }
        return data
    }
    
    static func generateTestPeerID() -> String {
        return "PEER" + UUID().uuidString.prefix(8)
    }
    
    // MARK: - Async Helpers
    
    static func waitFor(_ condition: @escaping () -> Bool, timeout: TimeInterval = TestConstants.defaultTimeout) async throws {
        let start = Date()
        while !condition() {
            if Date().timeIntervalSince(start) > timeout {
                throw TestError.timeout
            }
            try await sleep(0.01)
        }
    }

    @MainActor
    static func waitUntil(
        _ condition: @escaping () -> Bool,
        timeout: TimeInterval = TestConstants.defaultTimeout,
        pollInterval: TimeInterval = 0.01
    ) async -> Bool {
        let start = Date()
        while !condition() {
            if Date().timeIntervalSince(start) > timeout {
                return condition()
            }
            try? await sleep(pollInterval)
        }
        return true
    }
    
    static func expectAsync<T>(
        timeout: TimeInterval = TestConstants.defaultTimeout,
        operation: @escaping () async throws -> T
    ) async throws -> T {
        return try await withThrowingTaskGroup(of: T.self) { group in
            group.addTask {
                return try await operation()
            }
            
            group.addTask {
                try await sleep(1)
                throw TestError.timeout
            }
            
            let result = try await group.next()!
            group.cancelAll()
            return result
        }
    }
}

enum TestError: Error {
    case timeout
    case unexpectedValue
    case testFailure(String)
}

func sleep(_ seconds: TimeInterval) async throws {
    try await Task.sleep(nanoseconds: UInt64(seconds * 1_000_000_000))
}
