//
//  WalletConnect+.swift
//  damus
//
//  Created by Daniel D’Aquino on 2023-11-27.
//

import Foundation

// TODO: Eventually we should move these convenience functions into structured classes responsible for managing this type of functionality, such as `WalletModel`

extension WalletConnect {
    /// Creates and sends a subscription to an NWC relay requesting NWC responses to be sent back.
    ///
    /// Notes: This assumes there is already a listener somewhere else
    ///
    /// - Parameters:
    ///   - url: The Nostr Wallet Connect URL containing connection info to the NWC wallet
    ///   - pool: The RelayPool to send the subscription request through
    static func subscribe(url: WalletConnectURL, pool: RelayPool) async {
        var filter = NostrFilter(kinds: [.nwc_response])
        filter.authors = [url.pubkey]
        filter.pubkeys = [url.keypair.pubkey]
        filter.limit = 0
        let sub = NostrSubscribe(filters: [filter], sub_id: "nwc")

        await pool.send(.subscribe(sub), to: [url.relay], skip_ephemeral: false)
    }

    /// Sends out a request to pay an invoice to the NWC relay, and ensures that:
    /// 1. the NWC relay is connected and we are listening to NWC events
    /// 2. the NWC relay is connected and we are listening to NWC
    ///
    /// Note: This does not return information about whether the payment is succesful or not. The actual confirmation is handled elsewhere around `HomeModel` and `WalletModel`
    ///
    /// - Parameters:
    ///   - url: The NWC wallet connection URL
    ///   - pool: The relay pool to connect to
    ///   - post: The postbox to send events in
    ///   - delay: The delay before actually sending the request to the network _(this makes it possible to cancel a zap)_
    ///   - on_flush: A callback to call after the event has been flushed to the network
    /// - Returns: The Nostr Event that was sent to the network, representing the request that was made
    @discardableResult
    static func pay(url: WalletConnectURL, pool: RelayPool, post: PostBox, invoice: String, zap_request: NostrEvent?, delay: TimeInterval? = 5.0, on_flush: OnFlush? = nil) async -> NostrEvent? {
        
        let req = WalletConnect.Request.payZapRequest(invoice: invoice, zapRequest: zap_request)
        guard let ev = req.to_nostr_event(to_pk: url.pubkey, keypair: url.keypair) else {
            return nil
        }

        try? await pool.add_relay(.nwc(url: url.relay))   // Ensure the NWC relay is connected
        await WalletConnect.subscribe(url: url, pool: pool)      // Ensure we are listening to NWC updates from the relay
        await post.send(ev, to: [url.relay], skip_ephemeral: false, delay: delay, on_flush: on_flush)
        return ev
    }

    static func handle_zap_success(state: DamusState, resp: WalletConnect.FullWalletResponse) {
        // find the pending zap and mark it as pending-confirmed
        for kv in state.zaps.our_zaps {
            let zaps = kv.value
            
            for zap in zaps {
                guard case .pending(let pzap) = zap,
                      case .nwc(let nwc_state) = pzap.state,
                      case .postbox_pending(let nwc_req) = nwc_state.state,
                      nwc_req.id == resp.req_id
                else {
                    continue
                }
                
                if nwc_state.update_state(state: .confirmed) {
                    // notify the zaps model of an update so it can mark them as paid
                    state.events.get_cache_data(NoteId(pzap.target.id)).zaps_model.objectWillChange.send()
                    print("NWC success confirmed")
                }
                
                return
            }
        }
    }

    /// Handles a received Nostr Wallet Connect error
    static func handle_error(zapcache: Zaps, evcache: EventCache, resp: WalletConnect.FullWalletResponse) {
        // find a pending zap with the nwc request id associated with this response and remove it
        for kv in zapcache.our_zaps {
            let zaps = kv.value
            
            for zap in zaps {
                guard case .pending(let pzap) = zap,
                      case .nwc(let nwc_state) = pzap.state,
                      case .postbox_pending(let req) = nwc_state.state,
                      req.id == resp.req_id
                else {
                    continue
                }
                
                // remove the pending zap if there was an error
                let reqid = ZapRequestId(from_pending: pzap)
                remove_zap(reqid: reqid, zapcache: zapcache, evcache: evcache)
                return
            }
        }
    }
}
