//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift open source project
//
// Copyright (c) 2021-2022 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//

import Foundation
import XCTest

@testable import PackageCollectionsSigning
import SPMTestSupport
import TSCBasic

class SignatureTests: XCTestCase {
    func test_RS256_generateAndValidate_happyCase() throws {
        try skipIfUnsupportedPlatform()

        try fixture(name: "Collections", createGitRepo: false) { fixturePath in
            let jsonEncoder = JSONEncoder()
            let jsonDecoder = JSONDecoder()

            let payload = ["foo": "bar"]

            let certPath = fixturePath.appending(components: "Signing", "Test_rsa.cer")
            let certData: Data = try localFileSystem.readFileContents(certPath)
            let base64EncodedCert = certData.base64EncodedString()
            let certificate = try Certificate(derEncoded: certData)

            let header = Signature.Header(algorithm: .RS256, certChain: [base64EncodedCert])
            let privateKey = try RSAPrivateKey(pem: certRSAPrivateKey.bytes)
            let signature = try Signature.generate(for: payload, with: header, using: privateKey, jsonEncoder: jsonEncoder)

            let parsedSignature = try tsc_await { callback in
                Signature.parse(signature, certChainValidate: { _, cb in cb(.success([certificate])) }, jsonDecoder: jsonDecoder, callback: callback)
            }
            XCTAssertEqual(payload, try jsonDecoder.decode([String: String].self, from: parsedSignature.payload))
            XCTAssertEqual(header, parsedSignature.header)
        }
    }

    func test_RS256_generateAndValidate_keyMismatch() throws {
        try skipIfUnsupportedPlatform()

        try fixture(name: "Collections", createGitRepo: false) { fixturePath in
            let jsonEncoder = JSONEncoder()
            let jsonDecoder = JSONDecoder()

            let payload = ["foo": "bar"]

            let certPath = fixturePath.appending(components: "Signing", "Test_rsa.cer")
            let certData: Data = try localFileSystem.readFileContents(certPath)
            let base64EncodedCert = certData.base64EncodedString()
            let certificate = try Certificate(derEncoded: certData)

            let header = Signature.Header(algorithm: .RS256, certChain: [base64EncodedCert])
            // This is not cert's key so `parse` will fail
            let privateKey = try RSAPrivateKey(pem: rsaPrivateKey.bytes)
            let signature = try Signature.generate(for: payload, with: header, using: privateKey, jsonEncoder: jsonEncoder)

            XCTAssertThrowsError(try tsc_await { callback in
                Signature.parse(signature, certChainValidate: { _, cb in cb(.success([certificate])) }, jsonDecoder: jsonDecoder, callback: callback)
            }) { error in
                guard SignatureError.invalidSignature == error as? SignatureError else {
                    return XCTFail("Expected SignatureError.invalidSignature")
                }
            }
        }
    }

    func test_ES256_generateAndValidate_happyCase() throws {
        try skipIfUnsupportedPlatform()

        try fixture(name: "Collections", createGitRepo: false) { fixturePath in
            let jsonEncoder = JSONEncoder()
            let jsonDecoder = JSONDecoder()

            let payload = ["foo": "bar"]

            let certPath = fixturePath.appending(components: "Signing", "Test_ec.cer")
            let certData: Data = try localFileSystem.readFileContents(certPath)
            let base64EncodedCert = certData.base64EncodedString()
            let certificate = try Certificate(derEncoded: certData)

            let header = Signature.Header(algorithm: .ES256, certChain: [base64EncodedCert])
            let privateKey = try ECPrivateKey(pem: certECPrivateKey.bytes)
            let signature = try Signature.generate(for: payload, with: header, using: privateKey, jsonEncoder: jsonEncoder)

            let parsedSignature = try tsc_await { callback in
                Signature.parse(signature, certChainValidate: { _, cb in cb(.success([certificate])) }, jsonDecoder: jsonDecoder, callback: callback)
            }
            XCTAssertEqual(payload, try jsonDecoder.decode([String: String].self, from: parsedSignature.payload))
            XCTAssertEqual(header, parsedSignature.header)
        }
    }

    func test_ES256_generateAndValidate_keyMismatch() throws {
        try skipIfUnsupportedPlatform()

        try fixture(name: "Collections", createGitRepo: false) { fixturePath in
            let jsonEncoder = JSONEncoder()
            let jsonDecoder = JSONDecoder()

            let payload = ["foo": "bar"]

            let certPath = fixturePath.appending(components: "Signing", "Test_ec.cer")
            let certData: Data = try localFileSystem.readFileContents(certPath)
            let base64EncodedCert = certData.base64EncodedString()
            let certificate = try Certificate(derEncoded: certData)

            let header = Signature.Header(algorithm: .ES256, certChain: [base64EncodedCert])
            // This is not cert's key so `parse` will fail
            let privateKey = try ECPrivateKey(pem: ecPrivateKey.bytes)
            let signature = try Signature.generate(for: payload, with: header, using: privateKey, jsonEncoder: jsonEncoder)

            XCTAssertThrowsError(try tsc_await { callback in
                Signature.parse(signature, certChainValidate: { _, cb in cb(.success([certificate])) }, jsonDecoder: jsonDecoder, callback: callback)
            }) { error in
                guard SignatureError.invalidSignature == error as? SignatureError else {
                    return XCTFail("Expected SignatureError.invalidSignature")
                }
            }
        }
    }
}
