//
//  TestUtils.swift
//  CloudKitCodableTests
//
//  Created by Guilherme Rambo on 12/05/18.
//  Copyright © 2018 Guilherme Rambo. All rights reserved.
//

import CloudKit
import CryptoKit
import XCTest

@testable import CloudKitCodable

private final class _TestBundleClass {}

extension Bundle {
    static var testBundle: Bundle {
        return Bundle(for: _TestBundleClass.self)
    }
}

extension CKRecord {
    /// Creates a temporary record to simulate what would happen when encoding a CKRecord
    /// from a value that was previosly encoded to a CKRecord and had its system fields set
    static var systemFieldsDataForTesting: Data {
        let zoneID = CKRecordZone.ID(zoneName: "ZoneABCD", ownerName: "OwnerABCD")
        let recordID = CKRecord.ID(recordName: "RecordABCD", zoneID: zoneID)
        let testRecord = CKRecord(recordType: "Person", recordID: recordID)
        let coder = NSKeyedArchiver.init(requiringSecureCoding: true)
        coder.requiresSecureCoding = true
        testRecord.encodeSystemFields(with: coder)
        coder.finishEncoding()

        return coder.encodedData
    }

    static var testRecord: CKRecord {
        guard let url = Bundle.testBundle.url(forResource: "Rambo", withExtension: "ckrecord")
        else {
            fatalError("Required test asset Rambo.ckrecord not found")
        }

        return try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(try! Data(contentsOf: url))
            as! CKRecord
    }

    static var encryptedTestRecord: CKRecord {
        guard
            let url = Bundle.testBundle.url(
                forResource: "EncryptedRambo", withExtension: "ckrecord")
        else {
            fatalError("Required test asset EncryptedRambo.ckrecord not found")
        }

        return try! NSKeyedUnarchiver.unarchiveTopLevelObjectWithData(try! Data(contentsOf: url))
            as! CKRecord
    }
}

/// Validates that all fields in `record` match the expectations of encoding the test `Person` struct to a `CKRecord`
///
/// - Parameter record: A record generated by encoding `Person.rambo` with `CloudKitRecordEncoder`
func _validateRamboFields(in record: CKRecord) {
    XCTAssertEqual(record.recordType, "Person")
    XCTAssertEqual(record["name"] as? String, "Guilherme Rambo")
    XCTAssertEqual(record["age"] as? Int, 26)
    XCTAssertEqual(record["website"] as? String, "https://guilhermerambo.me")
    XCTAssertEqual(record["isDeveloper"] as? Bool, true)

    guard let asset = record["avatar"] as? CKAsset else {
        XCTFail("URL property with a file URL should encode to a CKAsset")
        return
    }

    XCTAssertEqual(
        asset.fileURL!.path,
        "/Users/inside/Library/Containers/br.com.guilhermerambo.CloudKitRoundTrip/Data/Library/Caches/CloudKit/aa007d03cf247aebef55372fa57c05d0dc3d8682/Assets/7644AD10-A5A5-4191-B4FF-EF412CC08A52.01ec4e7f3a4fe140bcc758ae2c4a30c7bbb04de8db"
    )

    XCTAssertNil(
        record[_CKSystemFieldsKeyName],
        "\(_CKSystemFieldsKeyName) should NOT be encoded to the record directly")
}

/// Validates that all fields in `record` match the expectations of encrypted encoding the test `Person` struct to a `CKRecord`
///
/// - Parameter record: A record generated by encoding `Person.rambo` with `CloudKitRecordEncoder`
func _validateEncryptedRamboFields(in record: CKRecord, withKey key: SymmetricKey) {
    XCTAssertEqual(record.recordType, ("Person"))

    guard let nameData = record["name"] as? Data else {
        XCTFail("name should be of type Data")
        return
    }

    guard let ageData = record["age"] as? Data else {
        XCTFail("age should be of type Data")
        return
    }

    guard let websiteData = record["website"] as? Data else {
        XCTFail("website should be of type Data")
        return
    }

    guard let isDeveloperData = record["isDeveloper"] as? Data else {
        XCTFail("isDeveloper should be of type Data")
        return
    }

    let encryptor = Encryptor(key)

    XCTAssertEqual(try encryptor.decrypt(String.self, encrypted: nameData), "Guilherme Rambo")
    XCTAssertEqual(try encryptor.decrypt(Int.self, encrypted: ageData), 26)
    XCTAssertEqual(
        try encryptor.decrypt(URL.self, encrypted: websiteData),
        URL(string: "https://guilhermerambo.me")!)
    XCTAssertEqual(try encryptor.decrypt(Bool.self, encrypted: isDeveloperData), true)

    guard let asset = record["avatar"] as? CKAsset else {
        XCTFail("URL property with a file URL should encode to a CKAsset")
        return
    }

    XCTAssertEqual(
        asset.fileURL!.path,
        "/Users/inside/Library/Containers/br.com.guilhermerambo.CloudKitRoundTrip/Data/Library/Caches/CloudKit/aa007d03cf247aebef55372fa57c05d0dc3d8682/Assets/7644AD10-A5A5-4191-B4FF-EF412CC08A52.01ec4e7f3a4fe140bcc758ae2c4a30c7bbb04de8db"
    )

    XCTAssertNil(
        record[_CKSystemFieldsKeyName],
        "\(_CKSystemFieldsKeyName) should NOT be encoded to the record directly")
}

extension Person {

    /// Sample person for tests
    static let rambo = Person(
        cloudKitSystemFields: nil,
        name: "Guilherme Rambo",
        age: 26,
        website: URL(string: "https://guilhermerambo.me")!,
        avatar: URL(
            fileURLWithPath:
                "/Users/inside/Library/Containers/br.com.guilhermerambo.CloudKitRoundTrip/Data/Library/Caches/CloudKit/aa007d03cf247aebef55372fa57c05d0dc3d8682/Assets/7644AD10-A5A5-4191-B4FF-EF412CC08A52.01ec4e7f3a4fe140bcc758ae2c4a30c7bbb04de8db"
        ),
        isDeveloper: true
    )

}

extension PersonWithCustomIdentifier {

    static let rambo = PersonWithCustomIdentifier(
        cloudKitSystemFields: nil,
        cloudKitIdentifier: "MY-ID",
        name: "Guilherme Rambo")
}
