import Foundation
import XCTest
import CryptoKit
@testable import SecretKit
@testable import SecretAgentKit

class AgentTests: XCTestCase {

    let stubWriter = StubFileHandleWriter()

    // MARK: Identity Listing

    func testEmptyStores() {
        let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestIdentities)
        let agent = Agent(storeList: SecretStoreList())
        agent.handle(reader: stubReader, writer: stubWriter)
        XCTAssertEqual(stubWriter.data, Constants.Responses.requestIdentitiesEmpty)
    }

    func testIdentitiesList() {
        let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestIdentities)
        let list = storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
        let agent = Agent(storeList: list)
        agent.handle(reader: stubReader, writer: stubWriter)
        XCTAssertEqual(stubWriter.data, Constants.Responses.requestIdentitiesMultiple)
    }

    // MARK: Signatures

    func testNoMatchingIdentities() {
        let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignatureWithNoneMatching)
        let list = storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
        let agent = Agent(storeList: list)
        agent.handle(reader: stubReader, writer: stubWriter)
//        XCTAssertEqual(stubWriter.data, Constants.Responses.requestFailure)
    }

    func testSignature() {
        let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature)
        let requestReader = OpenSSHReader(data: Constants.Requests.requestSignature[5...])
        _ = requestReader.readNextChunk()
        let dataToSign = requestReader.readNextChunk()
        let list = storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
        let agent = Agent(storeList: list)
        agent.handle(reader: stubReader, writer: stubWriter)
        let outer = OpenSSHReader(data: stubWriter.data[5...])
        let payload = outer.readNextChunk()
        let inner = OpenSSHReader(data: payload)
        _ = inner.readNextChunk()
        let signedData = inner.readNextChunk()
        let rsData = OpenSSHReader(data: signedData)
        var r = rsData.readNextChunk()
        var s = rsData.readNextChunk()
        // This is fine IRL, but it freaks out CryptoKit
        if r[0] == 0 {
            r.removeFirst()
        }
        if s[0] == 0 {
            s.removeFirst()
        }
        var rs = r
        rs.append(s)
        let signature = try! P256.Signing.ECDSASignature(rawRepresentation: rs)
        let valid = try! P256.Signing.PublicKey(x963Representation: Constants.Secrets.ecdsa256Secret.publicKey).isValidSignature(signature, for: dataToSign)
        XCTAssertTrue(valid)
    }

    // MARK: Witness protocol

    func testWitnessObjectionStopsRequest() {
        let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature)
        let list = storeList(with: [Constants.Secrets.ecdsa256Secret])
        let witness = StubWitness(speakNow: { _,_  in
            return true
        }, witness: { _, _ in })
        let agent = Agent(storeList: list, witness: witness)
        agent.handle(reader: stubReader, writer: stubWriter)
        XCTAssertEqual(stubWriter.data, Constants.Responses.requestFailure)
    }

    func testWitnessSignature() {
        let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature)
        let list = storeList(with: [Constants.Secrets.ecdsa256Secret])
        var witnessed = false
        let witness = StubWitness(speakNow: { _, trace  in
            return false
        }, witness: { _, trace in
            witnessed = true
        })
        let agent = Agent(storeList: list, witness: witness)
        agent.handle(reader: stubReader, writer: stubWriter)
        XCTAssertTrue(witnessed)
    }

    func testRequestTracing() {
        let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature)
        let list = storeList(with: [Constants.Secrets.ecdsa256Secret])
        var speakNowTrace: SigningRequestProvenance! = nil
        var witnessTrace: SigningRequestProvenance! = nil
        let witness = StubWitness(speakNow: { _, trace  in
            speakNowTrace = trace
            return false
        }, witness: { _, trace in
            witnessTrace = trace
        })
        let agent = Agent(storeList: list, witness: witness)
        agent.handle(reader: stubReader, writer: stubWriter)
        XCTAssertEqual(witnessTrace, speakNowTrace)
        XCTAssertEqual(witnessTrace.origin.displayName, "Finder")
        XCTAssertEqual(witnessTrace.origin.validSignature, true)
        XCTAssertEqual(witnessTrace.origin.parentPID, 1)
    }

    // MARK: Exception Handling

    func testSignatureException() {
        let stubReader = StubFileHandleReader(availableData: Constants.Requests.requestSignature)
        let list = storeList(with: [Constants.Secrets.ecdsa256Secret, Constants.Secrets.ecdsa384Secret])
        let store = list.stores.first?.base as! Stub.Store
        store.shouldThrow = true
        let agent = Agent(storeList: list)
        agent.handle(reader: stubReader, writer: stubWriter)
        XCTAssertEqual(stubWriter.data, Constants.Responses.requestFailure)
    }

    // MARK: Unsupported

    func testUnhandledAdd() {
        let stubReader = StubFileHandleReader(availableData: Constants.Requests.addIdentity)
        let agent = Agent(storeList: SecretStoreList())
        agent.handle(reader: stubReader, writer: stubWriter)
        XCTAssertEqual(stubWriter.data, Constants.Responses.requestFailure)
    }

}

extension AgentTests {

    func storeList(with secrets: [Stub.Secret]) -> SecretStoreList {
        let store = Stub.Store()
        store.secrets.append(contentsOf: secrets)
        let storeList = SecretStoreList()
        storeList.add(store: store)
        return storeList
    }

    enum Constants {

        enum Requests {
            static let requestIdentities = Data(base64Encoded: "AAAAAQs=")!
            static let addIdentity = Data(base64Encoded: "AAAAARE=")!
            static let requestSignatureWithNoneMatching = Data(base64Encoded: "AAABhA0AAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQAAAO8AAAAgbqmrqPUtJ8mmrtaSVexjMYyXWNqjHSnoto7zgv86xvcyAAAAA2dpdAAAAA5zc2gtY29ubmVjdGlvbgAAAAlwdWJsaWNrZXkBAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBEqCbkJbOHy5S1wVCaJoKPmpS0egM4frMqllgnlRRQ/Uvnn6EVS8oV03cPA2Bz0EdESyRKA/sbmn0aBtgjIwGELxu45UXEW1TEz6TxyS0u3vuIqR3Wo1CrQWRDnkrG/pBQAAAAA=")!
            static let requestSignature = Data(base64Encoded: "AAABRA0AAABoAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKzOkUiVJEcACMtAd9X7xalbc0FYZyhbmv2dsWl4IP2GWIi+RcsaHQNw+nAIQ8CKEYmLnl0VLDp5Ef8KMhgIy08AAADPAAAAIBIFsbCZ4/dhBmLNGHm0GKj7EJ4N8k/jXRxlyg+LFIYzMgAAAANnaXQAAAAOc3NoLWNvbm5lY3Rpb24AAAAJcHVibGlja2V5AQAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSszpFIlSRHAAjLQHfV+8WpW3NBWGcoW5r9nbFpeCD9hliIvkXLGh0DcPpwCEPAihGJi55dFSw6eRH/CjIYCMtPAAAAAA==")!
        }

        enum Responses {
            static let requestIdentitiesEmpty = Data(base64Encoded: "AAAABQwAAAAA")!
            static let requestIdentitiesMultiple = Data(base64Encoded: "AAABKwwAAAACAAAAaAAAABNlY2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQSszpFIlSRHAAjLQHfV+8WpW3NBWGcoW5r9nbFpeCD9hliIvkXLGh0DcPpwCEPAihGJi55dFSw6eRH/CjIYCMtPAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBLKSzA5q3jCb3q0JKigvcxfWVGrJ+bklpG0Zc9YzUwrbsh9SipvlSJi+sHQI+O0m88DOpRBAtuAHX60euD/Yv250tovN7/+MEFbXGZ/hLdd0BoFpWbLfJcQj806KJGlcDAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0")!
            static let requestFailure = Data(base64Encoded: "AAAAAQU=")!
        }

        enum Secrets {
            static let ecdsa256Secret =  Stub.Secret(keySize: 256, publicKey: Data(base64Encoded: "BKzOkUiVJEcACMtAd9X7xalbc0FYZyhbmv2dsWl4IP2GWIi+RcsaHQNw+nAIQ8CKEYmLnl0VLDp5Ef8KMhgIy08=")!, privateKey: Data(base64Encoded: "BKzOkUiVJEcACMtAd9X7xalbc0FYZyhbmv2dsWl4IP2GWIi+RcsaHQNw+nAIQ8CKEYmLnl0VLDp5Ef8KMhgIy09nw780wy/TSfUmzj15iJkV234AaCLNl+H8qFL6qK8VIg==")!)
            static let ecdsa384Secret =  Stub.Secret(keySize: 384, publicKey: Data(base64Encoded: "BLKSzA5q3jCb3q0JKigvcxfWVGrJ+bklpG0Zc9YzUwrbsh9SipvlSJi+sHQI+O0m88DOpRBAtuAHX60euD/Yv250tovN7/+MEFbXGZ/hLdd0BoFpWbLfJcQj806KJGlcDA==")!, privateKey: Data(base64Encoded: "BLKSzA5q3jCb3q0JKigvcxfWVGrJ+bklpG0Zc9YzUwrbsh9SipvlSJi+sHQI+O0m88DOpRBAtuAHX60euD/Yv250tovN7/+MEFbXGZ/hLdd0BoFpWbLfJcQj806KJGlcDHNapAOzrt9E+9QC4/KYoXS7Uw4pmdAz53uIj02tttiq3c0ZyIQ7XoscWWRqRrz8Kw==")!)
        }

    }

}