//
//  BlockStreamingTest.swift
//  ZcashLightClientKit-Unit-Tests
//
//  Created by Francisco Gindre on 5/25/21.
//

import XCTest
@testable import TestUtils
@testable import ZcashLightClientKit

// swiftlint:disable print_function_usage
class BlockStreamingTest: XCTestCase {
    override func setUpWithError() throws {
        try super.setUpWithError()
        logger = SampleLogger(logLevel: .debug)
    }

    override func tearDownWithError() throws {
        try super.tearDownWithError()
        try? FileManager.default.removeItem(at: __dataDbURL())
    }

    func testStream() async throws {
        let service = LightWalletGRPCService(
            host: LightWalletEndpointBuilder.eccTestnet.host,
            port: 9067,
            secure: true,
            singleCallTimeout: 1000,
            streamingCallTimeout: 100000
        )
        
        let latestHeight = try service.latestBlockHeight()
        
        let startHeight = latestHeight - 100_000
        var blocks: [ZcashCompactBlock] = []
        let stream = service.blockStream(startHeight: startHeight, endHeight: latestHeight)
        
        do {
            for try await compactBlock in stream {
                print("received block \(compactBlock.height)")
                blocks.append(compactBlock)
                print("progressHeight: \(compactBlock.height)")
                print("startHeight: \(startHeight)")
                print("targetHeight: \(latestHeight)")
            }
        } catch {
            XCTFail("failed with error: \(error)")
        }
    }
    
    func testStreamCancellation() async throws {
        let service = LightWalletGRPCService(
            host: LightWalletEndpointBuilder.eccTestnet.host,
            port: 9067,
            secure: true,
            singleCallTimeout: 10000,
            streamingCallTimeout: 10000
        )

        let storage = try TestDbBuilder.inMemoryCompactBlockStorage()
        let startHeight = try service.latestBlockHeight() - 100_000
        let processorConfig = CompactBlockProcessor.Configuration.standard(
            for: ZcashNetworkBuilder.network(for: .testnet),
            walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
        )

        let compactBlockProcessor = CompactBlockProcessor(
            service: service,
            storage: storage,
            backend: ZcashRustBackend.self,
            config: processorConfig
        )
        
        let cancelableTask = Task {
            do {
                try await compactBlockProcessor.compactBlockStreamDownload(
                    blockBufferSize: 10,
                    startHeight: startHeight
                )
            } catch {
                XCTAssertNotNil(error as? CancellationError)
            }
        }
        
        try await Task.sleep(nanoseconds: 3_000_000_000)
        cancelableTask.cancel()
    }
    
    func testStreamTimeout() async throws {
        let service = LightWalletGRPCService(
            host: LightWalletEndpointBuilder.eccTestnet.host,
            port: 9067,
            secure: true,
            singleCallTimeout: 1000,
            streamingCallTimeout: 3000
        )

        let storage = try TestDbBuilder.inMemoryCompactBlockStorage()
        let startHeight = try service.latestBlockHeight() - 100_000
        
        let processorConfig = CompactBlockProcessor.Configuration.standard(
            for: ZcashNetworkBuilder.network(for: .testnet),
            walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
        )

        let compactBlockProcessor = CompactBlockProcessor(
            service: service,
            storage: storage,
            backend: ZcashRustBackend.self,
            config: processorConfig
        )
        
        let date = Date()
        
        do {
            try await compactBlockProcessor.compactBlockStreamDownload(
                blockBufferSize: 10,
                startHeight: startHeight
            )
        } catch {
            if let lwdError = error as? LightWalletServiceError {
                switch lwdError {
                case .timeOut:
                    XCTAssert(true)
                default:
                    XCTFail("LWD Service error found, but should have been a timeLimit reached Error")
                }
            } else {
                XCTFail("Error should have been a timeLimit reached Error")
            }
        }
        
        let now = Date()
        
        let elapsed = now.timeIntervalSince(date)
        print("took \(elapsed) seconds")
    }
    
    func testBatch() async throws {
        let service = LightWalletGRPCService(
            host: LightWalletEndpointBuilder.eccTestnet.host,
            port: 9067,
            secure: true,
            singleCallTimeout: 300000,
            streamingCallTimeout: 10000
        )
        let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() )
        let targetHeight = try service.latestBlockHeight()
        let startHeight = targetHeight - 10_000
        
        let processorConfig = CompactBlockProcessor.Configuration.standard(
            for: ZcashNetworkBuilder.network(for: .testnet),
            walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
        )

        let compactBlockProcessor = CompactBlockProcessor(
            service: service,
            storage: storage,
            backend: ZcashRustBackend.self,
            config: processorConfig
        )
        
        let range = CompactBlockRange(uncheckedBounds: (startHeight, targetHeight))
        do {
            try await compactBlockProcessor.compactBlockBatchDownload(range: range)
            XCTAssertFalse(Task.isCancelled)
        } catch {
            XCTFail("failed with error: \(error)")
        }
    }

    func testBatchCancellation() async throws {
        let service = LightWalletGRPCService(
            host: LightWalletEndpointBuilder.eccTestnet.host,
            port: 9067,
            secure: true,
            singleCallTimeout: 300000,
            streamingCallTimeout: 10000
        )
        let storage = try TestDbBuilder.diskCompactBlockStorage(at: __dataDbURL() )
        let targetHeight = try service.latestBlockHeight()
        let startHeight = targetHeight - 100_000
        
        let processorConfig = CompactBlockProcessor.Configuration.standard(
            for: ZcashNetworkBuilder.network(for: .testnet),
            walletBirthday: ZcashNetworkBuilder.network(for: .testnet).constants.saplingActivationHeight
        )

        let compactBlockProcessor = CompactBlockProcessor(
            service: service,
            storage: storage,
            backend: ZcashRustBackend.self,
            config: processorConfig
        )
        
        let range = CompactBlockRange(uncheckedBounds: (startHeight, targetHeight))
        let cancelableTask = Task {
            do {
                try await compactBlockProcessor.compactBlockBatchDownload(range: range)
                XCTAssertTrue(Task.isCancelled)
            } catch {
                XCTFail("failed with error: \(error)")
            }
        }
        
        try await Task.sleep(nanoseconds: 3_000_000_000)
        cancelableTask.cancel()
    }
}
