//! # API Definitions
//!
//! This module, as closely as possible, defines the various APIs exposed by
//! different services to different clients. Although we do not have
//! compile-time guarantees that the services exposed exactly match the
//! definitions below, it is straightforward to compare the Axum routers and
//! handlers with the definitions below to ensure consistency.
//!
//! ## Guidelines
//!
//! All API requests and responses should return structs for upgradeability,
//! e.g. [`UserPkStruct`] instead of [`UserPk`].
//!
//! If an API method takes or returns nothing, make the type [`Empty`] and NOT
//! `()` (unit type). Using `()` makes it impossible to add optional fields in a
//! backwards-compatible way.
//!
//! Each endpoint should be documented with:
//! - 1) HTTP method e.g. `GET`
//! - 2) Endpoint e.g. `/v1/file`
//! - 3) Data used to make the request e.g. `VfsFileId`
//! - 4) The return type e.g. `MaybeVfsFile`
//!
//! The methods below should resemble the data actually sent across the wire.
//!
//! [`Empty`]: crate::types::Empty
//! [`UserPk`]: lexe_common::api::user::UserPk
//! [`UserPkStruct`]: lexe_common::api::user::UserPkStruct

#![deny(missing_docs)]
// We don't export our traits currently so auto trait stability is not relevant.
#![allow(async_fn_in_trait)]

use std::collections::HashSet;

use async_trait::async_trait;
use bytes::Bytes;
use lexe_common::api::{
    MegaId,
    auth::{
        BearerAuthRequestWire, BearerAuthResponse, BearerAuthToken,
        UserSignupRequestWire, UserSignupRequestWireV1,
    },
    fiat_rates::FiatRates,
    models::{
        SignMsgRequest, SignMsgResponse, Status, VerifyMsgRequest,
        VerifyMsgResponse,
    },
    provision::NodeProvisionRequest,
    revocable_clients::{
        CreateRevocableClientRequest, CreateRevocableClientResponse,
        GetRevocableClients, RevocableClients, UpdateClientRequest,
        UpdateClientResponse,
    },
    test_event::TestEventOp,
    user::{GetNewScidsRequest, MaybeScid, MaybeUser, Scids, UserPk},
    version::{CurrentEnclaves, EnclavesToProvision, NodeEnclave},
};
#[cfg(doc)]
use lexe_common::{
    api::MegaIdStruct,
    api::models::BroadcastedTxInfo,
    api::user::NodePkStruct,
    api::user::{UserPkSet, UserPkStruct},
    api::version::MeasurementStruct,
};
use lexe_crypto::ed25519;
use lexe_enclave::enclave::Measurement;
use lightning::events::Event;

#[cfg(doc)]
use crate::types::payments::{PaymentCreatedIndex, PaymentId};
use crate::{
    error::{
        BackendApiError, GatewayApiError, LspApiError, MegaApiError,
        NodeApiError, RunnerApiError,
    },
    models::{
        command::{
            BackupInfo, ClaimGeneratedHumanBitcoinAddress, CloseChannelRequest,
            CreateInvoiceRequest, CreateInvoiceResponse, CreateOfferRequest,
            CreateOfferResponse, DebugInfo, EnclavesToProvisionRequest,
            GetAddressResponse, GetGeneratedUsernameResponse, GetNewPayments,
            GetUpdatedPaymentMetadata, GetUpdatedPayments, HumanBitcoinAddress,
            ListChannelsResponse, NodeInfo, NodeInfoV1, OpenChannelRequest,
            OpenChannelResponse, PayInvoiceRequest, PayInvoiceResponse,
            PayOfferRequest, PayOfferResponse, PayOnchainRequest,
            PayOnchainResponse, PaymentCreatedIndexStruct,
            PaymentCreatedIndexes, PaymentIdStruct,
            PreflightCloseChannelRequest, PreflightCloseChannelResponse,
            PreflightOpenChannelRequest, PreflightOpenChannelResponse,
            PreflightPayInvoiceRequest, PreflightPayInvoiceResponse,
            PreflightPayOfferRequest, PreflightPayOfferResponse,
            PreflightPayOnchainRequest, PreflightPayOnchainResponse,
            ResyncRequest, SetupGDrive, UpdateHumanBitcoinAddress,
            UpdatePersonalNote, VecPaymentId,
        },
        nwc::{
            CreateNwcClientRequest, CreateNwcClientResponse, DbNwcClient,
            DbNwcClientFields, GetNwcClients, ListNwcClientResponse,
            NostrPkStruct, NostrSignedEvent, NwcRequest,
            UpdateNwcClientRequest, UpdateNwcClientResponse, VecDbNwcClient,
        },
        runner::{
            MegaNodeApiUserEvictRequest, MegaNodeApiUserRunRequest,
            MegaNodeApiUserRunResponse, UserFinishedRequest,
            UserLeaseRenewalRequest,
        },
    },
    types::{
        Empty,
        lnurl::{
            LnurlCallbackRequest, LnurlCallbackResponse, LnurlError,
            LnurlPayRequestWire,
        },
        payments::{
            DbPaymentMetadata, DbPaymentV1, DbPaymentV2, MaybeBasicPaymentV2,
            MaybeDbPaymentMetadata, MaybeDbPaymentV1, MaybeDbPaymentV2,
            VecBasicPaymentV1, VecBasicPaymentV2, VecDbPaymentMetadata,
            VecDbPaymentV1, VecDbPaymentV2,
        },
        ports::MegaPorts,
        sealed_seed::{MaybeSealedSeed, SealedSeed, SealedSeedId},
        username::UsernameStruct,
    },
    vfs::{
        MaybeVfsFile, VecVfsFile, VfsDirectory, VfsDirectoryList, VfsFile,
        VfsFileId,
    },
};

// TODO(max): To make clear that only upgradeable structs are being serialized,
// these methods should take e.g. `&UserPkStruct` instead of `UserPk`.

/// Defines the api that the backend exposes to the app (via the gateway).
pub trait AppBackendApi {
    /// POST /app/v2/signup [`ed25519::Signed<UserSignupRequestWire>`] ->
    /// [`Empty`]
    async fn signup_v2(
        &self,
        signed_req: &ed25519::Signed<&UserSignupRequestWire>,
    ) -> Result<Empty, BackendApiError>;

    /// POST /app/v1/signup [`ed25519::Signed<UserSignupRequestWireV1>`] ->
    /// [`Empty`]
    // TODO(phlip9): remove once all installed mobile clients above `app-v0.7.6`
    #[deprecated = "Use the `signup_v2` API instead"]
    async fn signup_v1(
        &self,
        signed_req: &ed25519::Signed<&UserSignupRequestWireV1>,
    ) -> Result<Empty, BackendApiError>;

    /// Query which node enclaves the user needs to provision to.
    ///
    /// POST /app/v1/enclaves_to_provision
    /// [`EnclavesToProvisionRequest`] -> [`EnclavesToProvision`]
    async fn enclaves_to_provision(
        &self,
        req: &EnclavesToProvisionRequest,
        auth: BearerAuthToken,
    ) -> Result<EnclavesToProvision, BackendApiError>;
}

/// Defines the api that the gateway directly exposes to the app.
pub trait AppGatewayApi {
    /// GET /app/v1/fiat_rates [`Empty`] -> [`FiatRates`]
    async fn get_fiat_rates(&self) -> Result<FiatRates, GatewayApiError>;

    /// Get the measurement and semver version of the latest node release.
    ///
    /// GET /app/v1/latest_release [`Empty`] -> [`NodeEnclave`]
    #[deprecated(note = "since app-v0.8.1: Use current_releases() instead")]
    async fn latest_release(&self) -> Result<NodeEnclave, GatewayApiError>;

    /// Get the measurements, enclave machine id and versions of all
    /// current node enclaves.
    ///
    /// GET /app/v1/current_releases [`Empty`] -> [`CurrentEnclaves`]
    #[deprecated(note = "since app-v0.8.8: Use current_enclaves() instead")]
    async fn current_releases(
        &self,
    ) -> Result<CurrentEnclaves, GatewayApiError>;

    /// Get the measurements, enclave machine id and versions of all
    /// current node enclaves.
    ///
    /// GET /app/v1/current_enclaves [`Empty`] -> [`CurrentEnclaves`]
    async fn current_enclaves(
        &self,
    ) -> Result<CurrentEnclaves, GatewayApiError>;
}

/// Defines the api that the node exposes to the app during provisioning.
pub trait AppNodeProvisionApi {
    /// Provision a node with the given [`Measurement`]. The provisioning node's
    /// remote attestation will be checked against the given [`Measurement`].
    ///
    /// POST /app/provision [`NodeProvisionRequest`] -> [`Empty`]
    async fn provision(
        &self,
        measurement: Measurement,
        data: NodeProvisionRequest,
    ) -> Result<Empty, NodeApiError>;
}

/// Defines the api that the node exposes to the app during normal operation.
pub trait AppNodeRunApi {
    /// GET /app/v2/node_info [`Empty`] -> [`NodeInfo`]
    async fn node_info(&self) -> Result<NodeInfo, NodeApiError>;

    /// GET /app/node_info [`Empty`] -> [`NodeInfoV1`]
    #[deprecated(note = "since node-v0.9.4: Use node_info instead")]
    async fn node_info_v1(&self) -> Result<NodeInfoV1, NodeApiError>;

    /// GET /app/debug_info [`Empty`] -> [`DebugInfo`]
    async fn debug_info(&self) -> Result<DebugInfo, NodeApiError>;

    /// GET /app/list_channels [`Empty`] -> [`ListChannelsResponse`]
    async fn list_channels(&self)
    -> Result<ListChannelsResponse, NodeApiError>;

    /// POST /app/sign_message [`SignMsgRequest`] -> [`SignMsgResponse`]
    ///
    /// Introduced in `node-v0.6.5`.
    async fn sign_message(
        &self,
        req: SignMsgRequest,
    ) -> Result<SignMsgResponse, NodeApiError>;

    /// POST /app/verify_message [`VerifyMsgRequest`] -> [`VerifyMsgResponse`]
    ///
    /// Introduced in `node-v0.6.5`.
    async fn verify_message(
        &self,
        req: VerifyMsgRequest,
    ) -> Result<VerifyMsgResponse, NodeApiError>;

    /// POST /app/open_channel [`OpenChannelRequest`] -> [`OpenChannelResponse`]
    ///
    /// Opens a channel to the LSP.
    async fn open_channel(
        &self,
        req: OpenChannelRequest,
    ) -> Result<OpenChannelResponse, NodeApiError>;

    /// POST /app/preflight_open_channel [`PreflightOpenChannelRequest`]
    ///                                  -> [`PreflightOpenChannelResponse`]
    ///
    /// Estimate on-chain fees required for an [`open_channel`] to the LSP.
    ///
    /// [`open_channel`]: AppNodeRunApi::open_channel
    async fn preflight_open_channel(
        &self,
        req: PreflightOpenChannelRequest,
    ) -> Result<PreflightOpenChannelResponse, NodeApiError>;

    /// POST /app/close_channel [`CloseChannelRequest`] -> [`Empty`]
    ///
    /// Closes a channel to the LSP.
    async fn close_channel(
        &self,
        req: CloseChannelRequest,
    ) -> Result<Empty, NodeApiError>;

    /// POST /app/preflight_close_channel [`PreflightCloseChannelRequest`]
    ///                                   -> [`PreflightCloseChannelResponse`]
    ///
    /// Estimate the on-chain fees required for a [`close_channel`].
    ///
    /// [`close_channel`]: AppNodeRunApi::close_channel
    async fn preflight_close_channel(
        &self,
        req: PreflightCloseChannelRequest,
    ) -> Result<PreflightCloseChannelResponse, NodeApiError>;

    /// POST /app/create_invoice [`CreateInvoiceRequest`]
    ///                          -> [`CreateInvoiceResponse`]
    async fn create_invoice(
        &self,
        req: CreateInvoiceRequest,
    ) -> Result<CreateInvoiceResponse, NodeApiError>;

    /// POST /app/pay_invoice [`PayInvoiceRequest`] -> [`PayInvoiceResponse`]
    async fn pay_invoice(
        &self,
        req: PayInvoiceRequest,
    ) -> Result<PayInvoiceResponse, NodeApiError>;

    /// POST /app/preflight_pay_invoice [`PreflightPayInvoiceRequest`]
    ///                                 -> [`PreflightPayInvoiceResponse`]
    ///
    /// This endpoint lets the app ask its node to "pre-flight" a BOLT11 invoice
    /// payment without going through with the actual payment. We verify as much
    /// as we can, find a route, and get the fee estimates.
    async fn preflight_pay_invoice(
        &self,
        req: PreflightPayInvoiceRequest,
    ) -> Result<PreflightPayInvoiceResponse, NodeApiError>;

    /// POST /app/create_offer [`CreateOfferRequest`] -> [`CreateOfferResponse`]
    ///
    /// Create a new Lightning offer (BOLT12).
    //
    // Added in `node-v0.7.3`.
    async fn create_offer(
        &self,
        req: CreateOfferRequest,
    ) -> Result<CreateOfferResponse, NodeApiError>;

    /// POST /app/pay_offer [`PayOfferRequest`] -> [`PayOfferResponse`]
    ///
    /// Pay a Lightning offer (BOLT12).
    //
    // Added in `node-v0.7.4`.
    async fn pay_offer(
        &self,
        req: PayOfferRequest,
    ) -> Result<PayOfferResponse, NodeApiError>;

    /// POST /app/preflight_pay_offer [`PreflightPayOfferRequest`]
    ///                               -> [`PreflightPayOfferResponse`]
    ///
    /// This endpoint lets the app ask its node to "pre-flight" a Lightning
    /// offer (BOLT12) payment without going through with the actual payment. We
    /// verify as much as we can, find a route, and get the fee estimates.
    //
    // Added in `node-v0.7.4`.
    async fn preflight_pay_offer(
        &self,
        req: PreflightPayOfferRequest,
    ) -> Result<PreflightPayOfferResponse, NodeApiError>;

    // TODO(phlip9): BOLT12: /app/request_refund

    /// POST /app/get_address [`Empty`] -> [`GetAddressResponse`]
    ///
    /// Returns an address which can be used to receive funds. It is unused
    /// unless there is an incoming tx and BDK hasn't detected it yet.
    async fn get_address(&self) -> Result<GetAddressResponse, NodeApiError>;

    /// POST /app/pay_onchain [`PayOnchainRequest`] -> [`PayOnchainResponse`]
    ///
    /// Pay bitcoin onchain. If the address is valid and we have sufficient
    /// onchain funds, this will broadcast a new transaction to the bitcoin
    /// mempool.
    async fn pay_onchain(
        &self,
        req: PayOnchainRequest,
    ) -> Result<PayOnchainResponse, NodeApiError>;

    /// POST /app/preflight_pay_onchain [`PreflightPayOnchainRequest`]
    ///                              -> [`PreflightPayOnchainResponse`]
    ///
    /// Returns estimated network fees for a potential onchain payment.
    async fn preflight_pay_onchain(
        &self,
        req: PreflightPayOnchainRequest,
    ) -> Result<PreflightPayOnchainResponse, NodeApiError>;

    /// GET /app/v1/payments/id [`PaymentIdStruct`] -> [`MaybeBasicPaymentV2`]
    //
    // Added in `node-v0.8.10`.
    async fn get_payment_by_id(
        &self,
        req: PaymentIdStruct,
    ) -> Result<MaybeBasicPaymentV2, NodeApiError>;

    /// POST /app/payments/indexes [`PaymentCreatedIndexes`]
    ///                         -> [`VecDbPaymentV1`]
    ///
    /// Fetch a batch of payments by their [`PaymentCreatedIndex`]s. This is
    /// typically used by a mobile client to poll for updates on payments
    /// which it currently has stored locally as "pending"; the intention is
    /// to check if any of these payments have been updated.
    //
    // We use POST because there may be a lot of idxs, which might be too large
    // to fit inside query parameters.
    #[deprecated(note = "since app-v0.8.9+29 and sdk-sidecar-v0.3.1: \
                         Use get_payments_by_ids instead")]
    async fn get_payments_by_indexes(
        &self,
        req: PaymentCreatedIndexes,
    ) -> Result<VecBasicPaymentV1, NodeApiError>;

    /// GET /app/payments/new [`GetNewPayments`] -> [`VecBasicPaymentV1`]
    #[deprecated(note = "since app-v0.8.9+29 and sdk-sidecar-v0.3.1: \
                         Use get_updated_payments instead")]
    async fn get_new_payments(
        &self,
        req: GetNewPayments,
    ) -> Result<VecBasicPaymentV1, NodeApiError>;

    /// GET /app/payments/updated [`GetUpdatedPayments`]
    ///                        -> [`VecBasicPaymentV2`]
    async fn get_updated_payments(
        &self,
        req: GetUpdatedPayments,
    ) -> Result<VecBasicPaymentV2, NodeApiError>;

    /// PUT /app/payments/note [`UpdatePersonalNote`] -> [`Empty`]
    async fn update_personal_note(
        &self,
        req: UpdatePersonalNote,
    ) -> Result<Empty, NodeApiError>;

    /// Lists all revocable clients.
    ///
    /// GET /app/clients [`GetRevocableClients`] -> [`RevocableClients`]
    // Added in `node-0.7.9`
    async fn get_revocable_clients(
        &self,
        req: GetRevocableClients,
    ) -> Result<RevocableClients, NodeApiError>;

    /// Creates a new revocable client. Returns the newly issued client cert.
    ///
    /// POST /app/clients [`CreateRevocableClientRequest`]
    ///                   -> [`CreateRevocableClientResponse`]
    // Added in `node-0.7.9`
    async fn create_revocable_client(
        &self,
        req: CreateRevocableClientRequest,
    ) -> Result<CreateRevocableClientResponse, NodeApiError>;

    /// Updates this revocable client. Returns the updated client.
    ///
    /// PUT /app/clients [`UpdateClientRequest`] -> [`UpdateClientResponse`]
    // Added in `node-0.7.9`
    async fn update_revocable_client(
        &self,
        req: UpdateClientRequest,
    ) -> Result<UpdateClientResponse, NodeApiError>;

    /// List all broadcasted transactions.
    ///
    /// GET /app/list_broadcasted_txs [`Empty`] -> [`Vec<BroadcastedTxInfo>`]
    async fn list_broadcasted_txs(
        &self,
    ) -> Result<serde_json::Value, NodeApiError>;

    /// Get the current status of Node backup.
    ///
    /// GET /app/backup [`Empty`] -> [`BackupInfo`]
    async fn backup_info(&self) -> Result<BackupInfo, NodeApiError>;

    /// Setup GDrive backup.
    ///
    /// POST /app/backup/gdrive [`SetupGDrive`] -> [`Empty`]
    async fn setup_gdrive(
        &self,
        req: SetupGDrive,
    ) -> Result<Empty, NodeApiError>;

    /// Get current user's human Bitcoin address.
    ///
    /// GET /app/human_bitcoin_address [`Empty`] -> [`HumanBitcoinAddress`]
    async fn get_human_bitcoin_address(
        &self,
    ) -> Result<HumanBitcoinAddress, NodeApiError>;

    /// Update current user's human Bitcoin address.
    ///
    /// PUT /app/human_bitcoin_address [`UsernameStruct`] ->
    /// [`HumanBitcoinAddress`]
    async fn update_human_bitcoin_address(
        &self,
        req: UsernameStruct,
    ) -> Result<HumanBitcoinAddress, NodeApiError>;

    /// GET /app/payment_address [`Empty`] -> [`HumanBitcoinAddress`]
    #[deprecated(note = "since app-v0.9.3 and sdk-sidecar-v0.4.2: \
                         Use get_human_bitcoin_address instead")]
    async fn get_payment_address(
        &self,
    ) -> Result<HumanBitcoinAddress, NodeApiError> {
        self.get_human_bitcoin_address().await
    }

    /// PUT /app/payment_address [`UsernameStruct`] -> [`HumanBitcoinAddress`]
    #[deprecated(note = "since app-v0.9.3 and sdk-sidecar-v0.4.2: \
                         Use update_human_bitcoin_address instead")]
    async fn update_payment_address(
        &self,
        req: UsernameStruct,
    ) -> Result<HumanBitcoinAddress, NodeApiError> {
        self.update_human_bitcoin_address(req).await
    }

    /// List NWC clients for the current user.
    /// Returns client info without sensitive data (no connection strings).
    ///
    /// GET /app/nwc_clients [`Empty`] -> [`ListNwcClientResponse`]
    async fn list_nwc_clients(
        &self,
    ) -> Result<ListNwcClientResponse, NodeApiError>;

    /// Create a new NWC client.
    /// Generates new keys and returns the connection string.
    ///
    /// POST /app/nwc_clients [`CreateNwcClientRequest`]
    ///                    -> [`CreateNwcClientResponse`]
    async fn create_nwc_client(
        &self,
        req: CreateNwcClientRequest,
    ) -> Result<CreateNwcClientResponse, NodeApiError>;

    /// Update an existing NWC client's label.
    ///
    /// PUT /app/nwc_clients [`UpdateNwcClientRequest`]
    ///                   -> [`UpdateNwcClientResponse`]
    async fn update_nwc_client(
        &self,
        req: UpdateNwcClientRequest,
    ) -> Result<UpdateNwcClientResponse, NodeApiError>;

    /// Delete an NWC client given its nostr client public key.
    ///
    /// DELETE /app/nwc_clients [`NostrPkStruct`] -> [`Empty`]
    async fn delete_nwc_client(
        &self,
        req: NostrPkStruct,
    ) -> Result<Empty, NodeApiError>;
}

/// The bearer auth API exposed by the backend (sometimes via the gateway) to
/// various consumers. This trait is defined separately from the
/// usual `ConsumerServiceApi` traits because `BearerAuthenticator` needs to
/// abstract over a generic implementor of [`BearerAuthBackendApi`].
#[async_trait]
pub trait BearerAuthBackendApi {
    /// POST /CONSUMER/bearer_auth [`ed25519::Signed<BearerAuthRequest>`]
    ///                         -> [`BearerAuthResponse`]
    ///
    /// Valid values for `CONSUMER` are: "app", "node" and "lsp".
    async fn bearer_auth(
        &self,
        signed_req: &ed25519::Signed<&BearerAuthRequestWire>,
    ) -> Result<BearerAuthResponse, BackendApiError>;
}

/// Defines the API the mega node exposes to the Lexe operators.
///
/// NOTE: For performance, this API does not use TLS! This API should only
/// contain methods for limited operational and lifecycle management endpoints.
pub trait LexeMegaApi {
    /// POST /lexe/run_user [`MegaNodeApiUserRunRequest`]
    ///                  -> [`MegaNodeApiUserRunResponse`]
    async fn run_user(
        &self,
        req: MegaNodeApiUserRunRequest,
    ) -> Result<MegaNodeApiUserRunResponse, MegaApiError>;

    /// POST /lexe/evict_user [`MegaNodeApiUserEvictRequest`] -> [`Empty`]
    async fn evict_user(
        &self,
        req: MegaNodeApiUserEvictRequest,
    ) -> Result<Empty, MegaApiError>;

    /// GET /lexe/status [`MegaIdStruct`] -> [`Status`]
    async fn status_mega(
        &self,
        mega_id: MegaId,
    ) -> Result<Status, MegaApiError>;

    /// POST /lexe/shutdown [`Empty`] -> [`Empty`]
    async fn shutdown_mega(&self) -> Result<Empty, MegaApiError>;
}

/// Defines the API the node exposes to the Lexe operators at run time.
///
/// NOTE: For performance, this API does not use TLS! This API should only
/// contain methods for limited operational and lifecycle management endpoints.
pub trait LexeNodeRunApi {
    /// GET /lexe/status [`UserPkStruct`] -> [`Status`]
    async fn status_run(&self, user_pk: UserPk)
    -> Result<Status, NodeApiError>;

    /// POST /lexe/resync [`ResyncRequest`] -> [`Empty`]
    ///
    /// Triggers an immediate resync of BDK and LDK. Optionally full sync the
    /// BDK wallet. Returns only once sync has either completed or timed out.
    async fn resync(&self, req: ResyncRequest) -> Result<Empty, NodeApiError>;

    /// POST /lexe/test_event [`TestEventOp`] -> [`Empty`]
    ///
    /// Calls the corresponding `TestEventReceiver` method.
    /// This endpoint can only be called by one caller at any one time.
    /// Does nothing and returns an error if called in prod.
    async fn test_event(&self, op: &TestEventOp)
    -> Result<Empty, NodeApiError>;

    /// GET /lexe/shutdown [`UserPkStruct`] -> [`Empty`]
    ///
    /// Not to be confused with [`LexeMegaApi::shutdown_mega`].
    async fn shutdown_run(
        &self,
        user_pk: UserPk,
    ) -> Result<Empty, NodeApiError>;

    /// POST /lexe/create_invoice [`CreateInvoiceRequest`] ->
    /// [`CreateInvoiceResponse`]
    async fn create_invoice(
        &self,
        req: CreateInvoiceRequest,
    ) -> Result<CreateInvoiceResponse, NodeApiError>;

    /// POST /lexe/nwc_request [`NwcRequest`] -> [`NostrSignedEvent`]
    ///
    /// Processes an encrypted NWC request from the nostr-bridge.
    /// The node will decrypt the request, execute the command, and return
    /// an encrypted response.
    async fn nwc_request(
        &self,
        req: NwcRequest,
    ) -> Result<NostrSignedEvent, NodeApiError>;
}

/// Defines the API the runner exposes to mega nodes.
pub trait MegaRunnerApi {
    /// POST /mega/ready [`MegaPorts`] -> [`Empty`]
    ///
    /// Indicates this mega node is initialized and ready to load user nodes.
    async fn mega_ready(
        &self,
        ports: &MegaPorts,
    ) -> Result<Empty, RunnerApiError>;

    /// POST /mega/activity [`UserPkSet`] -> [`Empty`]
    ///
    /// Indicates the meganode received some activity from its users.
    async fn activity(
        &self,
        user_pks: HashSet<UserPk>,
    ) -> Result<Empty, RunnerApiError>;

    /// POST /mega/user_finished [`UserFinishedRequest`] -> [`Empty`]
    ///
    /// Notifies the runner that a user has shut down,
    /// and that the user's lease can be terminated.
    async fn user_finished(
        &self,
        req: &UserFinishedRequest,
    ) -> Result<Empty, RunnerApiError>;
}

/// Defines the api that the backend exposes to the node.
pub trait NodeBackendApi {
    // --- Unauthenticated --- //

    /// GET /node/v1/user [`UserPkStruct`] -> [`MaybeUser`]
    async fn get_user(
        &self,
        user_pk: UserPk,
    ) -> Result<MaybeUser, BackendApiError>;

    /// GET /node/v1/sealed_seed [`SealedSeedId`] -> [`MaybeSealedSeed`]
    async fn get_sealed_seed(
        &self,
        data: &SealedSeedId,
    ) -> Result<MaybeSealedSeed, BackendApiError>;

    // --- Bearer authentication required --- //

    /// PUT /node/v1/sealed_seed [`SealedSeed`] -> [`Empty`]
    ///
    /// Idempotent: does nothing if the [`SealedSeedId`] already exists.
    async fn create_sealed_seed(
        &self,
        data: &SealedSeed,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// Delete all sealed seeds which have the given measurement and the user_pk
    /// of the authenticated user.
    ///
    /// DELETE /node/v1/sealed_seed [`MeasurementStruct`] -> [`Empty`]
    async fn delete_sealed_seeds(
        &self,
        measurement: Measurement,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// GET /node/v1/scids [`Empty`] -> [`Scids`]
    async fn get_scids(
        &self,
        auth: BearerAuthToken,
    ) -> Result<Scids, BackendApiError>;

    /// GET /node/v1/scid [`Empty`] -> [`MaybeScid`]
    // NOTE: Keep this def around until we can remove the backend handler.
    #[deprecated(note = "since lsp-v0.7.3: Use multi scid version instead")]
    async fn get_scid(
        &self,
        auth: BearerAuthToken,
    ) -> Result<MaybeScid, BackendApiError>;

    /// GET /node/v1/file [`VfsFileId`] -> [`MaybeVfsFile`]
    #[deprecated(note = "since node-v0.8.5: Use get_file instead")]
    async fn get_file_v1(
        &self,
        file_id: &VfsFileId,
        auth: BearerAuthToken,
    ) -> Result<MaybeVfsFile, BackendApiError>;

    /// GET /node/v2/file [`VfsFileId`] -> [`Bytes`] ([`VfsFile::data`])
    async fn get_file(
        &self,
        file_id: &VfsFileId,
        token: BearerAuthToken,
    ) -> Result<Bytes, BackendApiError>;

    /// POST /node/v1/file [`VfsFile`] -> [`Empty`]
    #[deprecated(note = "since node-v0.8.5: Use create_file instead")]
    async fn create_file_v1(
        &self,
        file: &VfsFile,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// POST /node/v2/file [`VfsFileId`] (query) + [`Bytes`] (body) -> [`Empty`]
    async fn create_file(
        &self,
        file_id: &VfsFileId,
        data: bytes::Bytes,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// PUT /node/v1/file [`VfsFile`] -> [`Empty`]
    #[deprecated(note = "since node-v0.8.5: Use upsert_file instead")]
    async fn upsert_file_v1(
        &self,
        file: &VfsFile,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// PUT /node/v2/file [`VfsFileId`] (query) + [`Bytes`] (body) -> [`Empty`]
    async fn upsert_file(
        &self,
        file_id: &VfsFileId,
        data: bytes::Bytes,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// DELETE /node/v1/file [`VfsFileId`] -> [`Empty`]
    ///
    /// Returns [`Ok`] only if exactly one row was deleted.
    async fn delete_file(
        &self,
        file_id: &VfsFileId,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// GET /node/v1/directory [`VfsDirectory`] -> [`VecVfsFile`]
    #[deprecated(note = "since node-v0.8.5: Use list_directory instead")]
    async fn get_directory_v1(
        &self,
        dir: &VfsDirectory,
        auth: BearerAuthToken,
    ) -> Result<VecVfsFile, BackendApiError>;

    /// GET /node/v2/directory [`VfsDirectory`] -> [`VecVfsFile`]
    async fn list_directory(
        &self,
        dir: &VfsDirectory,
        auth: BearerAuthToken,
    ) -> Result<VfsDirectoryList, BackendApiError>;

    /// GET /node/v1/payments [`PaymentCreatedIndexStruct`]
    ///                    -> [`MaybeDbPaymentV1`]
    #[deprecated(note = "since node-v0.8.8: Use get_payment_by_index instead")]
    async fn get_payment_by_index_v1(
        &self,
        req: PaymentCreatedIndexStruct,
        auth: BearerAuthToken,
    ) -> Result<MaybeDbPaymentV1, BackendApiError>;

    /// POST /node/v1/payments [`DbPaymentV1`] -> [`Empty`]
    #[deprecated(note = "since node-v0.8.10: Use upsert_payment instead")]
    async fn create_payment(
        &self,
        payment: DbPaymentV1,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// PUT /node/v1/payments [`DbPaymentV1`] -> [`Empty`]
    #[deprecated(note = "since node-v0.8.8: Use upsert_payment instead")]
    async fn upsert_payment_v1(
        &self,
        payment: DbPaymentV1,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// PUT /node/v2/payments [`DbPaymentV2`] -> [`Empty`]
    async fn upsert_payment(
        &self,
        payment: DbPaymentV2,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// GET /node/v1/payments/id [`PaymentIdStruct`] -> [`MaybeDbPaymentV1`]
    #[deprecated(note = "since node-v0.8.10: Use get_payment_by_id instead")]
    async fn get_payment_by_id_v1(
        &self,
        req: PaymentIdStruct,
        auth: BearerAuthToken,
    ) -> Result<MaybeDbPaymentV1, BackendApiError>;

    /// GET /node/v2/payments/id [`PaymentIdStruct`] -> [`MaybeDbPaymentV2`]
    async fn get_payment_by_id(
        &self,
        req: PaymentIdStruct,
        auth: BearerAuthToken,
    ) -> Result<MaybeDbPaymentV2, BackendApiError>;

    /// GET /node/v1/payments/index [`PaymentCreatedIndexStruct`]
    ///                          -> [`MaybeDbPaymentV1`]
    #[deprecated(note = "since node-v0.8.10: Use get_payment_by_id instead")]
    async fn get_payment_by_index(
        &self,
        req: PaymentCreatedIndexStruct,
        auth: BearerAuthToken,
    ) -> Result<MaybeDbPaymentV1, BackendApiError>;

    /// PUT /node/v1/payments/batch [`VecDbPaymentV1`] -> [`Empty`]
    ///
    /// ACID endpoint for upserting a batch of payments.
    #[deprecated(note = "since node-v0.8.8: Use upsert_payment_batch instead")]
    async fn upsert_payment_batch_v1(
        &self,
        payments: VecDbPaymentV1,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// PUT /node/v2/payments/batch [`VecDbPaymentV2`] -> [`Empty`]
    ///
    /// ACID endpoint for upserting a batch of payments.
    async fn upsert_payment_batch(
        &self,
        payments: VecDbPaymentV2,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// POST /node/v1/payments/indexes [`PaymentCreatedIndexes`]
    ///                             -> [`VecDbPaymentV1`]
    ///
    /// Fetch a batch of payments by their [`PaymentCreatedIndex`]s. This is
    /// typically used by a mobile client to poll for updates on payments
    /// which it currently has stored locally as "pending"; the intention is
    /// to check if any of these payments have been updated.
    //
    // We use POST because there may be a lot of idxs, which might be too large
    // to fit inside query parameters.
    #[deprecated(note = "since node-v0.8.10: Use get_payments_by_ids instead")]
    async fn get_payments_by_indexes(
        &self,
        req: PaymentCreatedIndexes,
        auth: BearerAuthToken,
    ) -> Result<VecDbPaymentV1, BackendApiError>;

    /// POST /node/v1/payments/ids [`VecPaymentId`]
    ///                         -> [`VecDbPaymentV2`]
    ///
    /// Fetch a batch of payments by their [`PaymentId`]s.
    //
    // We use POST because there may be a lot of ids,
    // which might be too large to fit inside query params.
    async fn get_payments_by_ids(
        &self,
        req: VecPaymentId,
        auth: BearerAuthToken,
    ) -> Result<VecDbPaymentV2, BackendApiError>;

    /// GET /node/v1/payments/new [`GetNewPayments`] -> [`VecDbPaymentV1`]
    ///
    /// Sync a batch of new payments to local storage, optionally starting from
    /// a known [`PaymentCreatedIndex`] (exclusive).
    /// Results are in ascending order, by `(created_at, payment_id)`.
    /// See [`GetNewPayments`] for more info.
    #[deprecated(note = "since app-v0.8.9+29 and sdk-sidecar-v0.3.1: \
                         Use get_updated_payments instead")]
    // NOTE: This fn is used in the app->node handler for /app/payments/new, so
    // the node->backend client code must remain until all app and sdk-sidecar
    // clients have been updated.
    async fn get_new_payments(
        &self,
        req: GetNewPayments,
        auth: BearerAuthToken,
    ) -> Result<VecDbPaymentV1, BackendApiError>;

    /// GET /node/v1/payments/updated [`GetUpdatedPayments`]
    ///                            -> [`VecDbPaymentV2`]
    async fn get_updated_payments(
        &self,
        req: GetUpdatedPayments,
        auth: BearerAuthToken,
    ) -> Result<VecDbPaymentV2, BackendApiError>;

    /// GET /node/v1/payments/pending -> [`VecDbPaymentV1`]
    ///
    /// Fetches all pending payments.
    #[deprecated(note = "since node-v0.8.10: Use get_pending_payments instead")]
    async fn get_pending_payments_v1(
        &self,
        auth: BearerAuthToken,
    ) -> Result<VecDbPaymentV1, BackendApiError>;

    /// GET /node/v2/payments/pending -> [`VecDbPaymentV2`]
    ///
    /// Fetches all pending payments.
    async fn get_pending_payments(
        &self,
        auth: BearerAuthToken,
    ) -> Result<VecDbPaymentV2, BackendApiError>;

    /// GET /node/v1/payments/final -> [`VecPaymentId`]
    ///
    /// Fetches the IDs of all finalized payments.
    #[deprecated(note = "since node-v0.8.8")]
    async fn get_finalized_payment_ids(
        &self,
        auth: BearerAuthToken,
    ) -> Result<VecPaymentId, BackendApiError>;

    /// PUT /node/v1/payments/metadata [`DbPaymentMetadata`] -> [`Empty`]
    async fn upsert_payment_metadata(
        &self,
        metadata: DbPaymentMetadata,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// PUT /node/v1/payments/metadata/batch [`VecDbPaymentMetadata`]
    ///                                   -> [`Empty`]
    ///
    /// ACID endpoint for upserting a batch of payments metadata.
    async fn upsert_payment_metadata_batch(
        &self,
        payments: VecDbPaymentMetadata,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// GET /node/v1/payments/metadata/id [`PaymentIdStruct`]
    ///                                -> [`MaybeDbPaymentMetadata`]
    ///
    /// Fetch a payment metadata by its [`PaymentId`].
    async fn get_payment_metadata_by_id(
        &self,
        req: PaymentIdStruct,
        token: BearerAuthToken,
    ) -> Result<MaybeDbPaymentMetadata, BackendApiError>;

    /// POST /node/v1/payments/metadata/ids [`VecPaymentId`]
    ///                                  -> [`VecDbPaymentMetadata`]
    ///
    /// Fetch a batch of payment metadata by their [`PaymentId`]s.
    //
    // We use POST because there may be a lot of ids,
    // which might be too large to fit inside query params.
    async fn get_payment_metadata_by_ids(
        &self,
        req: VecPaymentId,
        token: BearerAuthToken,
    ) -> Result<VecDbPaymentMetadata, BackendApiError>;

    /// GET /node/v1/payments/metadata/updated [`GetUpdatedPaymentMetadata`]
    ///                                     -> [`VecDbPaymentMetadata`]
    ///
    /// Get a batch of payment metadata in asc `(updated_at, payment_id)` order.
    async fn get_updated_payment_metadata(
        &self,
        req: GetUpdatedPaymentMetadata,
        auth: BearerAuthToken,
    ) -> Result<VecDbPaymentMetadata, BackendApiError>;

    /// PUT /node/v1/human_bitcoin_address [`UpdateHumanBitcoinAddress`]
    ///                         -> [`HumanBitcoinAddress`]
    ///
    /// Updates the human Bitcoin address (username and offer) of the given
    /// node.
    async fn update_human_bitcoin_address(
        &self,
        req: UpdateHumanBitcoinAddress,
        auth: BearerAuthToken,
    ) -> Result<HumanBitcoinAddress, BackendApiError>;

    /// PUT /node/v1/payment_address [`UpdateHumanBitcoinAddress`]
    ///                           -> [`HumanBitcoinAddress`]
    #[deprecated(note = "since node-v0.9.3: \
                         Use update_human_bitcoin_address instead")]
    async fn update_payment_address(
        &self,
        req: UpdateHumanBitcoinAddress,
        auth: BearerAuthToken,
    ) -> Result<HumanBitcoinAddress, BackendApiError> {
        self.update_human_bitcoin_address(req, auth).await
    }

    /// GET /node/v1/human_bitcoin_address [`Empty`] -> [`HumanBitcoinAddress`]
    ///
    /// Fetches the node's primary human Bitcoin address (username and offer).
    async fn get_human_bitcoin_address(
        &self,
        auth: BearerAuthToken,
    ) -> Result<HumanBitcoinAddress, BackendApiError>;

    /// GET /node/v1/payment_address [`Empty`] -> [`HumanBitcoinAddress`]
    #[deprecated(note = "since node-v0.9.3: \
                         Use get_human_bitcoin_address instead")]
    async fn get_payment_address(
        &self,
        auth: BearerAuthToken,
    ) -> Result<HumanBitcoinAddress, BackendApiError> {
        self.get_human_bitcoin_address(auth).await
    }

    /// POST /node/v1/claim_generated_human_bitcoin_address
    ///   [`ClaimGeneratedHumanBitcoinAddress`] -> [`Empty`]
    ///
    /// Claims a generated human Bitcoin address for the given node.
    async fn claim_generated_human_bitcoin_address(
        &self,
        req: ClaimGeneratedHumanBitcoinAddress,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;

    /// POST /node/v1/claim_generated_payment_address
    ///   [`ClaimGeneratedHumanBitcoinAddress`] -> [`Empty`]
    #[deprecated(note = "since node-v0.9.3: \
                         Use claim_generated_human_bitcoin_address instead")]
    async fn claim_generated_payment_address(
        &self,
        req: ClaimGeneratedHumanBitcoinAddress,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError> {
        self.claim_generated_human_bitcoin_address(req, auth).await
    }

    /// GET /node/v1/generated_username [`Empty`]
    ///                              -> [`GetGeneratedUsernameResponse`]
    ///
    /// Generates an available username for the authenticated user without
    /// storing it. The username is derived deterministically from the
    /// user's `user_pk`, with collision handling via numeric suffixes.
    async fn get_generated_username(
        &self,
        auth: BearerAuthToken,
    ) -> Result<GetGeneratedUsernameResponse, BackendApiError>;

    /// GET /node/v1/nwc_clients [`Empty`] -> [`VecDbNwcClient`]
    ///
    /// Fetches the node's NWC clients and optionally filters by
    /// client_nostr_pk.
    async fn get_nwc_clients(
        &self,
        req: GetNwcClients,
        auth: BearerAuthToken,
    ) -> Result<VecDbNwcClient, BackendApiError>;

    /// PUT /node/v1/nwc_clients [`DbNwcClientFields`] -> [`DbNwcClient`]
    ///
    /// Upserts a NWC client in the database.
    async fn upsert_nwc_client(
        &self,
        req: DbNwcClientFields,
        auth: BearerAuthToken,
    ) -> Result<DbNwcClient, BackendApiError>;

    /// DELETE /node/v1/nwc_clients [`NostrPkStruct`] -> [`Empty`]
    ///
    /// Deletes a NWC client given its nostr client public key.
    async fn delete_nwc_client(
        &self,
        req: NostrPkStruct,
        auth: BearerAuthToken,
    ) -> Result<Empty, BackendApiError>;
}

/// Defines the api that the LSP exposes to user nodes.
pub trait NodeLspApi {
    /// GET /node/v1/scids [`GetNewScidsRequest`] -> [`Scids`]
    async fn get_new_scids(
        &self,
        req: &GetNewScidsRequest,
    ) -> Result<Scids, LspApiError>;

    /// GET /node/v1/network_graph [`Empty`] -> [`Bytes`] (LDK-serialized graph)
    ///
    /// Introduced in node-v0.6.8 and lsp-v0.6.29.
    async fn get_network_graph(&self) -> Result<Bytes, LspApiError>;

    /// GET /node/v1/prob_scorer [`Empty`]
    ///                       -> [`Bytes`] (LDK-serialized probabilistic scorer)
    ///
    /// Introduced in node-v0.6.17 and lsp-v0.6.33.
    async fn get_prob_scorer(&self) -> Result<Bytes, LspApiError>;

    /// POST /node/v2/payment_path [`Bytes`] (LDK-serialized [`Event`])
    ///                         -> [`Empty`]
    ///
    /// Sends an anonymized successful or failed payment path to the LSP to
    /// update Lexe's shared network graph and improve payment reliability.
    ///
    /// Originally introduced in node-v0.6.17 and lsp-v0.6.33. All node-v0.9.5
    /// and older have a subtle bug in how they anonymize payment paths, so
    /// we now ignore all payment path feedback from <=node-v0.9.5
    async fn payment_path(&self, event: &Event) -> Result<Empty, LspApiError>;
}

/// Defines the api that the runner exposes to the user node.
pub trait NodeRunnerApi {
    /// POST /node/renew_lease [`UserLeaseRenewalRequest`] -> [`Empty`]
    ///
    /// Renew's a user node's lease with the megarunner.
    async fn renew_lease(
        &self,
        req: &UserLeaseRenewalRequest,
    ) -> Result<Empty, RunnerApiError>;

    /// POST /node/sync_success [`UserPkStruct`] -> [`Empty`]
    ///
    /// Indicates the node successfully completed sync.
    async fn sync_succ(&self, user_pk: UserPk)
    -> Result<Empty, RunnerApiError>;
}

/// Defines the api that the gateway exposes to the internet.
pub trait PublicGatewayApi {
    /// Get the LNURL pay request message, if any username has been set.
    /// Uses lnurl_callback endpoint as the callback.
    ///
    /// GET /.well-known/lnurlp/{username}
    async fn get_lnurl_pay_request(
        &self,
    ) -> Result<LnurlPayRequestWire, LnurlError>;

    /// Resolves the invoice given a LNURL pay request previously generated.
    /// Ciphertext is a base64 encoded string of the username and
    /// other metadata information.
    ///
    /// GET /public/v1/lnurl_callback/{encoded_params}
    async fn lnurl_callback(
        &self,
        req: LnurlCallbackRequest,
    ) -> Result<LnurlCallbackResponse, LnurlError>;
}
