//
//  BalanceBreakdownStore.swift
//  secant-testnet
//
//  Created by Lukáš Korba on 04.08.2022.
//

import Foundation
import ComposableArchitecture
import ZcashLightClientKit
import DerivationTool
import MnemonicClient
import NumberFormatter
import Utils
import Generated
import WalletStorage
import SDKSynchronizer
import Models

public typealias BalanceBreakdownStore = Store<BalanceBreakdownReducer.State, BalanceBreakdownReducer.Action>
public typealias BalanceBreakdownViewStore = ViewStore<BalanceBreakdownReducer.State, BalanceBreakdownReducer.Action>

public struct BalanceBreakdownReducer: ReducerProtocol {
    private enum CancelId { case timer }
    let networkType: NetworkType
    
    public struct State: Equatable {
        @PresentationState public var alert: AlertState<Action>?
        public var autoShieldingThreshold: Zatoshi
        public var latestBlock: String
        public var shieldedBalance: Balance
        public var shieldingFunds: Bool
        public var synchronizerStatusSnapshot: SyncStatusSnapshot
        public var transparentBalance: Balance

        public var totalBalance: Zatoshi {
            shieldedBalance.data.total + transparentBalance.data.total
        }

        public var isShieldableBalanceAvailable: Bool {
            transparentBalance.data.verified.amount >= autoShieldingThreshold.amount
        }

        public var isShieldingButtonDisabled: Bool {
            shieldingFunds || !isShieldableBalanceAvailable
        }
        
        public init(
            autoShieldingThreshold: Zatoshi,
            latestBlock: String,
            shieldedBalance: Balance,
            shieldingFunds: Bool,
            synchronizerStatusSnapshot: SyncStatusSnapshot,
            transparentBalance: Balance
        ) {
            self.autoShieldingThreshold = autoShieldingThreshold
            self.latestBlock = latestBlock
            self.shieldedBalance = shieldedBalance
            self.shieldingFunds = shieldingFunds
            self.synchronizerStatusSnapshot = synchronizerStatusSnapshot
            self.transparentBalance = transparentBalance
        }
    }

    public enum Action: Equatable {
        case alert(PresentationAction<Action>)
        case onAppear
        case onDisappear
        case shieldFunds
        case shieldFundsSuccess
        case shieldFundsFailure(ZcashError)
        case synchronizerStateChanged(SynchronizerState)
        case updateLatestBlock
    }

    @Dependency(\.derivationTool) var derivationTool
    @Dependency(\.mainQueue) var mainQueue
    @Dependency(\.mnemonic) var mnemonic
    @Dependency(\.numberFormatter) var numberFormatter
    @Dependency(\.sdkSynchronizer) var sdkSynchronizer
    @Dependency(\.walletStorage) var walletStorage

    public init(networkType: NetworkType) {
        self.networkType = networkType
    }
    
    public var body: some ReducerProtocol<State, Action> {
        Reduce { state, action in
            switch action {
            case .alert(.presented(let action)):
                return Effect.send(action)

            case .alert(.dismiss):
                state.alert = nil
                return .none

            case .alert:
                return .none

            case .onAppear:
                return sdkSynchronizer.stateStream()
                    .throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
                    .map(BalanceBreakdownReducer.Action.synchronizerStateChanged)
                    .eraseToEffect()
                    .cancellable(id: CancelId.timer, cancelInFlight: true)

            case .onDisappear:
                return .cancel(id: CancelId.timer)

            case .shieldFunds:
                state.shieldingFunds = true
                return .run { [state] send in
                    do {
                        let storedWallet = try walletStorage.exportWallet()
                        let seedBytes = try mnemonic.toSeed(storedWallet.seedPhrase.value())
                        let spendingKey = try derivationTool.deriveSpendingKey(seedBytes, 0, networkType)

                        _ = try await sdkSynchronizer.shieldFunds(spendingKey, Memo(string: ""), state.autoShieldingThreshold)

                        await send(.shieldFundsSuccess)
                    } catch {
                        await send(.shieldFundsFailure(error.toZcashError()))
                    }
                }

            case .shieldFundsSuccess:
                state.shieldingFunds = false
                state.alert = AlertState.shieldFundsSuccess()
                return .none

            case .shieldFundsFailure(let error):
                state.shieldingFunds = false
                state.alert = AlertState.shieldFundsFailure(error)
                return .none

            case .synchronizerStateChanged(let latestState):
                state.shieldedBalance = latestState.shieldedBalance.redacted
                state.transparentBalance = latestState.transparentBalance.redacted
                
                let snapshot = SyncStatusSnapshot.snapshotFor(state: latestState.syncStatus)
                if snapshot.syncStatus != state.synchronizerStatusSnapshot.syncStatus {
                    state.synchronizerStatusSnapshot = snapshot
                }

                return Effect.send(.updateLatestBlock)

            case .updateLatestBlock:
                let latestBlockNumber = sdkSynchronizer.latestState().latestBlockHeight
                let latestBlock = numberFormatter.string(NSDecimalNumber(value: latestBlockNumber))
                state.latestBlock = "\(String(describing: latestBlock ?? ""))"
                return .none
            }
        }
    }
}

// MARK: Alerts

extension AlertState where Action == BalanceBreakdownReducer.Action {
    public static func shieldFundsFailure(_ error: ZcashError) -> AlertState {
        AlertState {
            TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Failure.title)
        } message: {
            TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Failure.message(error.message, error.code.rawValue))
        }
    }
    
    public static func shieldFundsSuccess() -> AlertState {
        AlertState {
            TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Success.title)
        } message: {
            TextState(L10n.BalanceBreakdown.Alert.ShieldFunds.Success.message)
        }
    }
}

// MARK: - Placeholders

extension BalanceBreakdownReducer.State {
    public static let placeholder = BalanceBreakdownReducer.State(
        autoShieldingThreshold: Zatoshi(1_000_000),
        latestBlock: L10n.General.unknown,
        shieldedBalance: Balance.zero,
        shieldingFunds: false,
        synchronizerStatusSnapshot: .placeholder,
        transparentBalance: Balance.zero
    )
}

extension BalanceBreakdownStore {
    public static let placeholder = BalanceBreakdownStore(
        initialState: .placeholder,
        reducer: BalanceBreakdownReducer(networkType: .testnet)
    )
}
