import XCTest
import CryptoKit
@testable import ByteLystPlatformSDK

final class BLFieldEncryptTests: XCTestCase {

    private var key: SymmetricKey!
    private let dekId = "dek_user1_test"

    override func setUp() {
        super.setUp()
        key = BLFieldEncrypt.generateKey()
    }

    // MARK: - Encrypt / Decrypt Roundtrip

    func testEncryptDecryptRoundtrip() throws {
        let plaintext = "Hello, World!"
        let encrypted = try BLFieldEncrypt.encrypt(plaintext, key: key, dekId: dekId)
        let decrypted = try BLFieldEncrypt.decrypt(encrypted, key: key)
        XCTAssertEqual(decrypted, plaintext)
    }

    func testEncryptDecryptEmptyString() throws {
        let plaintext = ""
        let encrypted = try BLFieldEncrypt.encrypt(plaintext, key: key, dekId: dekId)
        let decrypted = try BLFieldEncrypt.decrypt(encrypted, key: key)
        XCTAssertEqual(decrypted, plaintext)
    }

    func testEncryptDecryptUnicode() throws {
        let plaintext = "こんにちは世界 🌍 مرحبا Ñoño"
        let encrypted = try BLFieldEncrypt.encrypt(plaintext, key: key, dekId: dekId)
        let decrypted = try BLFieldEncrypt.decrypt(encrypted, key: key)
        XCTAssertEqual(decrypted, plaintext)
    }

    func testEncryptDecryptLargePayload() throws {
        let plaintext = String(repeating: "A", count: 100_000)
        let encrypted = try BLFieldEncrypt.encrypt(plaintext, key: key, dekId: dekId)
        let decrypted = try BLFieldEncrypt.decrypt(encrypted, key: key)
        XCTAssertEqual(decrypted, plaintext)
    }

    // MARK: - EncryptedField Structure

    func testEncryptedFieldHasCorrectSentinel() throws {
        let encrypted = try BLFieldEncrypt.encrypt("test", key: key, dekId: dekId)
        XCTAssertTrue(encrypted.__encrypted)
        XCTAssertEqual(encrypted.v, 1)
        XCTAssertEqual(encrypted.alg, "aes-256-gcm")
        XCTAssertEqual(encrypted.dekId, dekId)
    }

    func testEncryptedFieldHasCorrectHexLengths() throws {
        let encrypted = try BLFieldEncrypt.encrypt("test", key: key, dekId: dekId)
        // IV: 12 bytes = 24 hex chars
        XCTAssertEqual(encrypted.iv.count, 24)
        // Tag: 16 bytes = 32 hex chars
        XCTAssertEqual(encrypted.tag.count, 32)
        // ct should be non-empty hex
        XCTAssertFalse(encrypted.ct.isEmpty)
    }

    func testEachEncryptionProducesUniqueIV() throws {
        let a = try BLFieldEncrypt.encrypt("same", key: key, dekId: dekId)
        let b = try BLFieldEncrypt.encrypt("same", key: key, dekId: dekId)
        XCTAssertNotEqual(a.iv, b.iv, "Each encryption should use a unique IV")
        XCTAssertNotEqual(a.ct, b.ct, "Ciphertext should differ with different IVs")
    }

    // MARK: - AAD (Additional Authenticated Data)

    func testEncryptDecryptWithAAD() throws {
        let plaintext = "secret data"
        let aad = "user123:notes"
        let encrypted = try BLFieldEncrypt.encrypt(plaintext, key: key, dekId: dekId, aad: aad)
        let decrypted = try BLFieldEncrypt.decrypt(encrypted, key: key, aad: aad)
        XCTAssertEqual(decrypted, plaintext)
    }

    func testDecryptWithWrongAADFails() throws {
        let encrypted = try BLFieldEncrypt.encrypt("secret", key: key, dekId: dekId, aad: "correct")
        XCTAssertThrowsError(try BLFieldEncrypt.decrypt(encrypted, key: key, aad: "wrong"))
    }

    func testDecryptWithMissingAADFails() throws {
        let encrypted = try BLFieldEncrypt.encrypt("secret", key: key, dekId: dekId, aad: "some-aad")
        XCTAssertThrowsError(try BLFieldEncrypt.decrypt(encrypted, key: key))
    }

    // MARK: - Wrong Key

    func testDecryptWithWrongKeyFails() throws {
        let encrypted = try BLFieldEncrypt.encrypt("secret", key: key, dekId: dekId)
        let wrongKey = BLFieldEncrypt.generateKey()
        XCTAssertThrowsError(try BLFieldEncrypt.decrypt(encrypted, key: wrongKey))
    }

    // MARK: - Key Size Validation

    func testEncryptRejectsShortKey() {
        let shortKey = SymmetricKey(size: .bits128)
        XCTAssertThrowsError(try BLFieldEncrypt.encrypt("test", key: shortKey, dekId: dekId)) { error in
            guard let encError = error as? BLFieldEncryptError,
                  case .invalidKeySize = encError else {
                XCTFail("Expected invalidKeySize error")
                return
            }
        }
    }

    // MARK: - Key from Hex

    func testKeyFromHex() throws {
        let hex = String(repeating: "ab", count: 32) // 64 hex chars = 32 bytes
        let key = try BLFieldEncrypt.keyFromHex(hex)
        let encrypted = try BLFieldEncrypt.encrypt("test", key: key, dekId: dekId)
        let decrypted = try BLFieldEncrypt.decrypt(encrypted, key: key)
        XCTAssertEqual(decrypted, "test")
    }

    func testKeyFromHexRejectsInvalidLength() {
        XCTAssertThrowsError(try BLFieldEncrypt.keyFromHex("aabb"))
    }

    // MARK: - isEncrypted Type Guard

    func testIsEncryptedWithValidField() throws {
        let encrypted = try BLFieldEncrypt.encrypt("test", key: key, dekId: dekId)
        XCTAssertTrue(BLFieldEncrypt.isEncrypted(encrypted))
    }

    func testIsEncryptedWithNil() {
        XCTAssertFalse(BLFieldEncrypt.isEncrypted(nil as BLEncryptedField?))
    }

    func testIsEncryptedWithDictionary() {
        let dict: [String: Any] = [
            "__encrypted": true,
            "v": 1,
            "alg": "aes-256-gcm",
            "ct": "abcd",
            "iv": "1234",
            "tag": "5678",
            "dekId": "dek_test",
        ]
        XCTAssertTrue(BLFieldEncrypt.isEncrypted(dict))
    }

    func testIsEncryptedWithPlainString() {
        XCTAssertFalse(BLFieldEncrypt.isEncrypted("just a string" as Any))
    }

    func testIsEncryptedWithIncompleteDict() {
        let dict: [String: Any] = ["__encrypted": true, "v": 1]
        XCTAssertFalse(BLFieldEncrypt.isEncrypted(dict))
    }

    // MARK: - JSON Codable Roundtrip

    func testEncryptedFieldCodableRoundtrip() throws {
        let encrypted = try BLFieldEncrypt.encrypt("codable test", key: key, dekId: dekId)
        let encoder = JSONEncoder()
        let data = try encoder.encode(encrypted)
        let decoder = JSONDecoder()
        let decoded = try decoder.decode(BLEncryptedField.self, from: data)
        let decrypted = try BLFieldEncrypt.decrypt(decoded, key: key)
        XCTAssertEqual(decrypted, "codable test")
    }

    // MARK: - Hex Helpers

    func testDataHexRoundtrip() {
        let original = Data([0x00, 0x0f, 0xff, 0xab, 0xcd])
        let hex = original.hexString
        XCTAssertEqual(hex, "000fffabcd")
        let restored = Data(hexString: hex)
        XCTAssertEqual(restored, original)
    }

    func testDataHexInvalidString() {
        XCTAssertNil(Data(hexString: "xyz"))
        XCTAssertNil(Data(hexString: "a")) // odd length
    }
}
