import UIKit
import SwiftUI
import AVFoundation
import ComposableArchitecture
import ZcashLightClientKit
import AudioServices
import DiskSpaceChecker
import Utils
import Models
import Generated
import ReviewRequest
import TransactionList
import Scan
import SyncProgress
import RestoreWalletStorage

public typealias HomeStore = Store<HomeReducer.State, HomeReducer.Action>
public typealias HomeViewStore = ViewStore<HomeReducer.State, HomeReducer.Action>

public struct HomeReducer: Reducer {
    private let CancelStateId = UUID()
    private let CancelEventId = UUID()

    public struct State: Equatable {
        public enum Destination: Equatable {
            case notEnoughFreeDiskSpace
        }

        @PresentationState public var alert: AlertState<Action>?
        public var destination: Destination?
        public var canRequestReview = false
        public var isRestoringWallet = false
        public var requiredTransactionConfirmations = 0
        public var scanState: Scan.State
        public var shieldedBalance: Zatoshi
        public var synchronizerStatusSnapshot: SyncStatusSnapshot
        public var syncProgressState: SyncProgressReducer.State
        public var walletConfig: WalletConfig
        public var totalBalance: Zatoshi
        public var transactionListState: TransactionListReducer.State
        public var migratingDatabase = true
        // TODO: [#311] - Get the ZEC price from the SDK, https://github.com/Electric-Coin-Company/zashi-ios/issues/311
        public var zecPrice = Decimal(140.0)

        public var totalCurrencyBalance: Zatoshi {
            Zatoshi.from(decimal: shieldedBalance.decimalValue.decimalValue * zecPrice)
        }

        public var isSendButtonDisabled: Bool {
            shieldedBalance.amount == 0
        }
        
        public var isProcessingZeroAvailableBalance: Bool {
            totalBalance.amount != shieldedBalance.amount && shieldedBalance.amount == 0
        }

        public init(
            destination: Destination? = nil,
            canRequestReview: Bool = false,
            isRestoringWallet: Bool = false,
            requiredTransactionConfirmations: Int = 0,
            scanState: Scan.State,
            shieldedBalance: Zatoshi,
            synchronizerStatusSnapshot: SyncStatusSnapshot,
            syncProgressState: SyncProgressReducer.State,
            totalBalance: Zatoshi = .zero,
            transactionListState: TransactionListReducer.State,
            walletConfig: WalletConfig,
            zecPrice: Decimal = Decimal(140.0)
        ) {
            self.destination = destination
            self.canRequestReview = canRequestReview
            self.isRestoringWallet = isRestoringWallet
            self.requiredTransactionConfirmations = requiredTransactionConfirmations
            self.scanState = scanState
            self.shieldedBalance = shieldedBalance
            self.synchronizerStatusSnapshot = synchronizerStatusSnapshot
            self.syncProgressState = syncProgressState
            self.totalBalance = totalBalance
            self.transactionListState = transactionListState
            self.walletConfig = walletConfig
            self.zecPrice = zecPrice
        }
    }

    public enum Action: Equatable {
        case alert(PresentationAction<Action>)
        case balanceBreakdown
        case debugMenuStartup
        case foundTransactions
        case onAppear
        case onDisappear
        case resolveReviewRequest
        case restoreWalletTask
        case restoreWalletValue(Bool)
        case retrySync
        case reviewRequestFinished
        case showSynchronizerErrorAlert(ZcashError)
        case synchronizerStateChanged(RedactableSynchronizerState)
        case syncFailed(ZcashError)
        case syncProgress(SyncProgressReducer.Action)
        case updateDestination(HomeReducer.State.Destination?)
        case updateTransactionList([TransactionState])
        case transactionList(TransactionListReducer.Action)
    }
    
    @Dependency(\.audioServices) var audioServices
    @Dependency(\.diskSpaceChecker) var diskSpaceChecker
    @Dependency(\.mainQueue) var mainQueue
    @Dependency(\.restoreWalletStorage) var restoreWalletStorage
    @Dependency(\.reviewRequest) var reviewRequest
    @Dependency(\.sdkSynchronizer) var sdkSynchronizer
    @Dependency(\.zcashSDKEnvironment) var zcashSDKEnvironment

    public init() { }
    
    public var body: some Reducer<State, Action> {
        Scope(state: \.transactionListState, action: /Action.transactionList) {
            TransactionListReducer()
        }

        Scope(state: \.syncProgressState, action: /Action.syncProgress) {
            SyncProgressReducer()
        }

        Reduce { state, action in
            switch action {
            case .onAppear:
                state.requiredTransactionConfirmations = zcashSDKEnvironment.requiredTransactionConfirmations

                if diskSpaceChecker.hasEnoughFreeSpaceForSync() {
                    return .merge(
                        Effect.send(.updateDestination(nil)),
                        .publisher {
                            sdkSynchronizer.stateStream()
                                .throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
                                .map { $0.redacted }
                                .map(HomeReducer.Action.synchronizerStateChanged)
                        }
                        .cancellable(id: CancelStateId, cancelInFlight: true),
                        .publisher {
                            sdkSynchronizer.eventStream()
                                .throttle(for: .seconds(0.2), scheduler: mainQueue, latest: true)
                                .compactMap {
                                    if case SynchronizerEvent.foundTransactions = $0 {
                                        return HomeReducer.Action.foundTransactions
                                    }
                                    return nil
                                }
                        }
                        .cancellable(id: CancelEventId, cancelInFlight: true)
                    )
                } else {
                    return Effect.send(.updateDestination(.notEnoughFreeDiskSpace))
                }
                
            case .onDisappear:
                return .concatenate(
                    .cancel(id: CancelStateId),
                    .cancel(id: CancelEventId)
                )

            case .resolveReviewRequest:
                if reviewRequest.canRequestReview() {
                    state.canRequestReview = true
                    return .run { _ in
                        reviewRequest.reviewRequested()
                    }
                }
                return .none
                
            case .restoreWalletTask:
                return .run { send in
                    for await value in await restoreWalletStorage.value() {
                        await send(.restoreWalletValue(value))
                    }
                }

            case .restoreWalletValue(let value):
                state.isRestoringWallet = value
                return .none

            case .reviewRequestFinished:
                state.canRequestReview = false
                return .none
                                
            case .updateTransactionList:
                return .none
                
            case .synchronizerStateChanged(let latestState):
                let snapshot = SyncStatusSnapshot.snapshotFor(state: latestState.data.syncStatus)

                if snapshot.syncStatus != .unprepared {
                    state.migratingDatabase = false
                }

                state.synchronizerStatusSnapshot = snapshot
                let accountBalance = latestState.data.accountBalance?.data
                
                state.shieldedBalance = (accountBalance?.saplingBalance.spendableValue ?? .zero) + (accountBalance?.orchardBalance.spendableValue ?? .zero)
                state.totalBalance = (accountBalance?.saplingBalance.total() ?? .zero) + (accountBalance?.orchardBalance.total() ?? .zero)

                switch snapshot.syncStatus {
                case .error(let error):
                    return Effect.send(.showSynchronizerErrorAlert(error.toZcashError()))

                case .upToDate:
                    return .run { _ in
                        reviewRequest.syncFinished()
                    }

                default:
                    return .none
                }

            case .syncProgress:
                return .none

            case .foundTransactions:
                return .run { _ in
                    reviewRequest.foundTransactions()
                }
                
            case .updateDestination(let destination):
                state.destination = destination
                return .none
                
            case .transactionList:
                return .none
                
            case .retrySync:
                return .run { send in
                    do {
                        try await sdkSynchronizer.start(true)
                    } catch {
                        await send(.syncFailed(error.toZcashError()))
                    }
                }

            case .showSynchronizerErrorAlert:
                return .none
                
            case .debugMenuStartup:
                return .none
                
            case .syncFailed:
                return .none

            case .balanceBreakdown:
                return .none
                
            case .alert(.presented(let action)):
                return Effect.send(action)

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

            case .alert:
                return .none
            }
        }
    }
}

// MARK: - Store

extension HomeStore {
    func historyStore() -> TransactionListStore {
        self.scope(
            state: \.transactionListState,
            action: HomeReducer.Action.transactionList
        )
    }
}

// MARK: - ViewStore

extension HomeViewStore {
    func bindingForDestination(_ destination: HomeReducer.State.Destination) -> Binding<Bool> {
        self.binding(
            get: { $0.destination == destination },
            send: { isActive in
                return .updateDestination(isActive ? destination : nil)
            }
        )
    }
}

// MARK: Placeholders

extension HomeReducer.State {
    public static var initial: Self {
        .init(
            scanState: .initial,
            shieldedBalance: .zero,
            synchronizerStatusSnapshot: .initial,
            syncProgressState: .initial,
            transactionListState: .initial,
            walletConfig: .initial
        )
    }
}

extension HomeStore {
    public static var placeholder: HomeStore {
        HomeStore(
            initialState: .initial
        ) {
            HomeReducer()
        }
    }

    public static var error: HomeStore {
        HomeStore(
            initialState: .init(
                scanState: .initial,
                shieldedBalance: .zero,
                synchronizerStatusSnapshot: .snapshotFor(
                    state: .error(ZcashError.synchronizerNotPrepared)
                ),
                syncProgressState: .initial,
                transactionListState: .initial,
                walletConfig: .initial
            )
        ) {
            HomeReducer()
        }
    }
}
