//
//  MockTransactionRepository.swift
//  ZcashLightClientKit-Unit-Tests
//
//  Created by Francisco Gindre on 12/6/19.
//

import Foundation

@testable import ZcashLightClientKit
class MockTransactionRepository: TransactionRepository {
    
    var unminedCount: Int
    var receivedCount: Int
    var sentCount: Int
    
    var transactions: [ConfirmedTransactionEntity] = []
    var reference: [Kind] = []
    var sentTransactions: [ConfirmedTransaction] = []
    var receivedTransactions: [ConfirmedTransaction] = []
    
    var allCount: Int {
        receivedCount + sentCount
    }
    
    init(unminedCount: Int, receivedCount: Int, sentCount: Int) {
        self.unminedCount = unminedCount
        self.receivedCount = receivedCount
        self.sentCount = sentCount
    }
    
    func generate() {
        
        var txArray = [ConfirmedTransactionEntity]()
        reference = referenceArray()
        for i in 0 ..< reference.count {
            txArray.append(mockTx(index: i, kind: reference[i]))
        }
        transactions = txArray
    }
    
    func countAll() throws -> Int {
        allCount
    }
    
    func countUnmined() throws -> Int {
        unminedCount
    }
    
    func findBy(id: Int) throws -> TransactionEntity? {
        transactions.first(where: {$0.id == id})?.transactionEntity
    }
    
    func findBy(rawId: Data) throws -> TransactionEntity? {
        transactions.first(where: {$0.rawTransactionId == rawId})?.transactionEntity
    }
    
    func findAllSentTransactions(offset: Int, limit: Int) throws -> [ConfirmedTransactionEntity]? {
        guard let indices = reference.indices(where: { $0 == .sent }) else { return nil }
        
        let sentTxs = indices.map { (idx) -> ConfirmedTransactionEntity in
            transactions[idx]
        }
        return slice(txs: sentTxs, offset: offset, limit: limit)
    }
    
    
    func findAllReceivedTransactions(offset: Int, limit: Int) throws -> [ConfirmedTransactionEntity]? {
          guard let indices = reference.indices(where: { $0 == .received }) else { return nil }
              
        let receivedTxs = indices.map { (idx) -> ConfirmedTransactionEntity in
                  transactions[idx]
              }
        return slice(txs: receivedTxs, offset: offset, limit: limit)
    }
    
    func findAll(offset: Int, limit: Int) throws -> [ConfirmedTransactionEntity]? {
        transactions
    }
    
    func lastScannedHeight() throws -> BlockHeight {
        return 700000
    }
    
    func isInitialized() throws -> Bool {
        true
    }
    
    func findEncodedTransactionBy(txId: Int) -> EncodedTransaction? {
        nil
    }
    
    enum Kind {
        case sent
        case received
    }
    
    func referenceArray() -> [Kind] {
        var template = [Kind]()
        
        for _ in 0 ..< sentCount {
            template.append(.sent)
        }
        for _ in 0 ..< receivedCount {
            template.append(.received)
        }
        return template.shuffled()
    }
    
    
    func mockTx(index: Int, kind: Kind) -> ConfirmedTransactionEntity {
        switch kind {
        case .received:
            return mockReceived(index)
        case .sent:
            return mockSent(index)
        }
    }
    
    func mockSent(_ index: Int) -> ConfirmedTransactionEntity {
        ConfirmedTransaction(toAddress: "some_address", expiryHeight: BlockHeight.max, minedHeight: randomBlockHeight(), noteId: index, blockTimeInSeconds: randomTimeInterval(), transactionIndex: index, raw: Data(), id: index, value: Int.random(in: 1 ... ZcashSDK.ZATOSHI_PER_ZEC), memo: nil, rawTransactionId: Data())
    }
    
    
    func mockReceived(_ index: Int) -> ConfirmedTransactionEntity {
         ConfirmedTransaction(toAddress: nil, expiryHeight: BlockHeight.max, minedHeight: randomBlockHeight(), noteId: index, blockTimeInSeconds: randomTimeInterval(), transactionIndex: index, raw: Data(), id: index, value: Int.random(in: 1 ... ZcashSDK.ZATOSHI_PER_ZEC), memo: nil, rawTransactionId: Data())
    }
    
    func randomBlockHeight() -> BlockHeight {
        BlockHeight.random(in: ZcashSDK.SAPLING_ACTIVATION_HEIGHT ... 1_000_000)
    }
    func randomTimeInterval() -> TimeInterval {
        Double.random(in: Date().timeIntervalSince1970 - 1000000.0 ... Date().timeIntervalSince1970)
    }
    
    func slice(txs: [ConfirmedTransactionEntity], offset: Int, limit: Int) -> [ConfirmedTransactionEntity] {
        guard offset < txs.count else { return [] }
        
        return Array(txs[offset ..< min(offset + limit, txs.count - offset)])
    }
}

extension MockTransactionRepository.Kind: Equatable {}

extension Array {
    func indices(where f: (_ element: Element) -> Bool) -> [Int]? {
        guard self.count > 0 else { return nil }
        var idx = [Int]()
        for i in 0 ..< self.count {
            if f(self[i]) {
                idx.append(i)
            }
        }
        
        guard idx.count > 0 else { return nil }
        return idx
        
    }
}
