import XCTest
@testable import bitchat

final class VerificationServiceTests: XCTestCase {
    func test_buildMyQRString_roundTripsSuccessfully() throws {
        let (service, noise) = makeService()
        let nickname = "alice-\(UUID().uuidString)"
        let npub = "npub1testvalue"

        let qrString = try XCTUnwrap(service.buildMyQRString(nickname: nickname, npub: npub))
        let parsed = try XCTUnwrap(service.verifyScannedQR(qrString))

        XCTAssertEqual(parsed.nickname, nickname)
        XCTAssertEqual(parsed.npub, npub)
        XCTAssertEqual(parsed.noiseKeyHex, noise.getStaticPublicKeyData().hexEncodedString())
        XCTAssertEqual(parsed.signKeyHex, noise.getSigningPublicKeyData().hexEncodedString())
    }

    func test_buildMyQRString_returnsCachedValueForSameInputs() throws {
        let (service, _) = makeService()
        let nickname = "cache-\(UUID().uuidString)"

        let first = try XCTUnwrap(service.buildMyQRString(nickname: nickname, npub: nil))
        let second = try XCTUnwrap(service.buildMyQRString(nickname: nickname, npub: nil))

        XCTAssertEqual(first, second)
    }

    func test_verifyScannedQR_rejectsExpiredPayload() throws {
        let (service, noise) = makeService()
        let oldTimestamp = Int64(Date().addingTimeInterval(-3600).timeIntervalSince1970)
        let qrString = try makeSignedQR(
            noise: noise,
            nickname: "expired-\(UUID().uuidString)",
            npub: nil,
            ts: oldTimestamp
        )

        XCTAssertNil(service.verifyScannedQR(qrString, maxAge: 60))
    }

    func test_verifyScannedQR_rejectsTamperedSignature() throws {
        let (service, noise) = makeService()
        let badSignature = Data(repeating: 0xAA, count: 64)
        let qrString = try makeSignedQR(
            noise: noise,
            nickname: "tampered-\(UUID().uuidString)",
            npub: nil,
            ts: Int64(Date().timeIntervalSince1970),
            signatureOverride: badSignature
        )

        XCTAssertNil(service.verifyScannedQR(qrString))
    }

    func test_buildVerifyChallenge_roundTripsThroughNoisePayload() throws {
        let (service, _) = makeService()
        let noiseKeyHex = String(repeating: "ab", count: 32)
        let nonce = Data([0x01, 0x02, 0x03, 0x04])

        let encoded = service.buildVerifyChallenge(noiseKeyHex: noiseKeyHex, nonceA: nonce)
        let payload = try XCTUnwrap(NoisePayload.decode(encoded))
        let parsed = try XCTUnwrap(service.parseVerifyChallenge(payload.data))

        XCTAssertEqual(payload.type, .verifyChallenge)
        XCTAssertEqual(parsed.noiseKeyHex, noiseKeyHex)
        XCTAssertEqual(parsed.nonceA, nonce)
    }

    func test_buildVerifyResponse_roundTripsAndVerifiesSignature() throws {
        let (service, noise) = makeService()
        let noiseKeyHex = String(repeating: "cd", count: 32)
        let nonce = Data([0x10, 0x20, 0x30, 0x40, 0x50])

        let encoded = try XCTUnwrap(service.buildVerifyResponse(noiseKeyHex: noiseKeyHex, nonceA: nonce))
        let payload = try XCTUnwrap(NoisePayload.decode(encoded))
        let parsed = try XCTUnwrap(service.parseVerifyResponse(payload.data))

        XCTAssertEqual(payload.type, .verifyResponse)
        XCTAssertEqual(parsed.noiseKeyHex, noiseKeyHex)
        XCTAssertEqual(parsed.nonceA, nonce)
        XCTAssertTrue(
            service.verifyResponseSignature(
                noiseKeyHex: parsed.noiseKeyHex,
                nonceA: parsed.nonceA,
                signature: parsed.signature,
                signerPublicKeyHex: noise.getSigningPublicKeyData().hexEncodedString()
            )
        )
        XCTAssertFalse(
            service.verifyResponseSignature(
                noiseKeyHex: parsed.noiseKeyHex,
                nonceA: Data([0xFF]),
                signature: parsed.signature,
                signerPublicKeyHex: noise.getSigningPublicKeyData().hexEncodedString()
            )
        )
    }

    private func makeService() -> (VerificationService, NoiseEncryptionService) {
        // The service consumes Noise identity operations through the
        // Transport's narrow noise* wrappers; the mock transport's backing
        // encryption service is returned for direct assertions.
        let transport = MockTransport()
        let service = VerificationService()
        service.configure(with: transport)
        return (service, transport.mockNoiseService)
    }

    private func makeSignedQR(
        noise: NoiseEncryptionService,
        nickname: String,
        npub: String?,
        ts: Int64,
        signatureOverride: Data? = nil
    ) throws -> String {
        var payload = VerificationService.VerificationQR(
            v: 1,
            noiseKeyHex: noise.getStaticPublicKeyData().hexEncodedString(),
            signKeyHex: noise.getSigningPublicKeyData().hexEncodedString(),
            npub: npub,
            nickname: nickname,
            ts: ts,
            nonceB64: Data((0..<16).map(UInt8.init)).base64EncodedString(),
            sigHex: ""
        )
        let signature = try XCTUnwrap(signatureOverride ?? noise.signData(payload.canonicalBytes()))
        payload.sigHex = signature.hexEncodedString()
        return payload.toURLString()
    }
}
