// RPC Client for connecting to Archipelago backend

export interface RPCOptions {
  method: string
  params?: Record<string, unknown>
  timeout?: number
}

export interface RPCResponse<T> {
  result?: T
  error?: {
    code: number
    message: string
    data?: unknown
  }
}

class RPCClient {
  private baseUrl: string

  constructor(baseUrl: string = '/rpc/v1') {
    this.baseUrl = baseUrl
  }

  async call<T>(options: RPCOptions): Promise<T> {
    const { method, params = {}, timeout = 30000 } = options
    const maxRetries = 3

    for (let attempt = 0; attempt < maxRetries; attempt++) {
      const controller = new AbortController()
      const timeoutId = setTimeout(() => controller.abort(), timeout)

      try {
        const response = await fetch(this.baseUrl, {
          method: 'POST',
          credentials: 'include', // Important for session cookies
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({ method, params }),
          signal: controller.signal,
        })

        clearTimeout(timeoutId)

        if (!response.ok) {
          const err = new Error(`HTTP ${response.status}: ${response.statusText}`)
          const isRetryable = response.status === 502 || response.status === 503
          if (isRetryable && attempt < maxRetries - 1) {
            await new Promise((r) => setTimeout(r, 600 * (attempt + 1)))
            continue
          }
          throw err
        }

        const data: RPCResponse<T> = await response.json()

        if (data.error) {
          throw new Error(data.error.message || 'RPC Error')
        }

        return data.result as T
      } catch (error) {
        clearTimeout(timeoutId)
        if (error instanceof Error) {
          if (error.name === 'AbortError') {
            const timeoutErr = new Error('Request timeout')
            if (attempt < maxRetries - 1) {
              await new Promise((r) => setTimeout(r, 600 * (attempt + 1)))
              continue
            }
            throw timeoutErr
          }
          const msg = error.message
          const isRetryable = /502|503|Bad Gateway|fetch|network/i.test(msg)
          if (isRetryable && attempt < maxRetries - 1) {
            await new Promise((r) => setTimeout(r, 600 * (attempt + 1)))
            continue
          }
          throw error
        }
        throw new Error('Unknown error occurred')
      }
    }

    throw new Error('Request failed after retries')
  }

  // Convenience methods
  async login(password: string): Promise<{ requires_totp?: boolean } | null> {
    return this.call({
      method: 'auth.login',
      params: {
        password,
      },
    })
  }

  async loginTotp(code: string): Promise<{ success: boolean }> {
    return this.call({ method: 'auth.login.totp', params: { code } })
  }

  async loginBackup(code: string): Promise<{ success: boolean }> {
    return this.call({ method: 'auth.login.backup', params: { code } })
  }

  async totpSetupBegin(password: string): Promise<{
    qr_svg: string
    secret_base32: string
    pending_token: string
  }> {
    return this.call({ method: 'auth.totp.setup.begin', params: { password } })
  }

  async totpSetupConfirm(params: {
    code: string
    password: string
    pendingToken: string
  }): Promise<{ enabled: boolean; backup_codes: string[] }> {
    return this.call({ method: 'auth.totp.setup.confirm', params })
  }

  async totpDisable(password: string, code: string): Promise<{ disabled: boolean }> {
    return this.call({ method: 'auth.totp.disable', params: { password, code } })
  }

  async totpStatus(): Promise<{ enabled: boolean }> {
    return this.call({ method: 'auth.totp.status', params: {} })
  }

  async changePassword(params: {
    currentPassword: string
    newPassword: string
    alsoChangeSsh?: boolean
  }): Promise<{ success: boolean }> {
    return this.call({
      method: 'auth.changePassword',
      params: {
        currentPassword: params.currentPassword,
        newPassword: params.newPassword,
        alsoChangeSsh: params.alsoChangeSsh ?? true,
      },
    })
  }

  async logout(): Promise<void> {
    return this.call({
      method: 'auth.logout',
      params: {},
    })
  }

  async completeOnboarding(): Promise<boolean> {
    return this.call({
      method: 'auth.onboardingComplete',
      params: {},
    })
  }

  async isOnboardingComplete(): Promise<boolean> {
    return this.call({
      method: 'auth.isOnboardingComplete',
      params: {},
    })
  }

  async resetOnboarding(): Promise<boolean> {
    return this.call({
      method: 'auth.resetOnboarding',
      params: {},
    })
  }

  async getNodeDid(): Promise<{ did: string; pubkey: string }> {
    return this.call({
      method: 'node.did',
      params: {},
    })
  }

  async signChallenge(challenge: string): Promise<{ signature: string }> {
    return this.call({
      method: 'node.signChallenge',
      params: { challenge },
    })
  }

  async createBackup(passphrase: string): Promise<{
    version: number
    did: string
    pubkey: string
    kid: string
    encrypted: boolean
    blob: string
    timestamp: string
  }> {
    return this.call({
      method: 'node.createBackup',
      params: { passphrase },
    })
  }

  async publishNostrIdentity(): Promise<{ event_id: string; success: number; failed: number }> {
    return this.call({
      method: 'node.nostr-publish',
      params: {},
    })
  }

  async getNostrPubkey(): Promise<{ nostr_pubkey: string }> {
    return this.call({
      method: 'node.nostr-pubkey',
      params: {},
    })
  }

  async listPeers(): Promise<{ peers: Array<{ onion: string; pubkey: string; name?: string }> }> {
    return this.call({
      method: 'node-list-peers',
      params: {},
    })
  }

  async addPeer(params: { onion: string; pubkey: string; name?: string }): Promise<{ peers: unknown[] }> {
    return this.call({
      method: 'node-add-peer',
      params,
    })
  }

  async removePeer(pubkey: string): Promise<{ peers: unknown[] }> {
    return this.call({
      method: 'node-remove-peer',
      params: { pubkey },
    })
  }

  async sendMessageToPeer(onion: string, message: string): Promise<{ ok: boolean; sent_to: string }> {
    return this.call({
      method: 'node-send-message',
      params: { onion, message },
      timeout: 90000,
    })
  }

  async checkPeerReachable(onion: string): Promise<{ onion: string; reachable: boolean }> {
    return this.call({
      method: 'node-check-peer',
      params: { onion },
      timeout: 35000,
    })
  }

  async getReceivedMessages(): Promise<{ messages: Array<{ from_pubkey: string; message: string; timestamp: string }> }> {
    return this.call({
      method: 'node-messages-received',
      params: {},
    })
  }

  async discoverNodes(): Promise<{ nodes: Array<{ did: string; onion: string; pubkey: string; node_address: string }> }> {
    return this.call({
      method: 'node-nostr-discover',
      params: {},
      timeout: 20000,
    })
  }

  async getTorAddress(): Promise<{ tor_address: string | null }> {
    return this.call({
      method: 'node.tor-address',
      params: {},
    })
  }

  async verifyNostrRevoked(): Promise<{
    revoked: boolean
    nostr_pubkey: string
    latest_content?: string
    error?: string
  }> {
    return this.call({
      method: 'node-nostr-verify-revoked',
      params: {},
      timeout: 25000,
    })
  }

  async echo(message: string): Promise<string> {
    return this.call({
      method: 'server.echo',
      params: { message },
    })
  }

  async getSystemTime(): Promise<{ now: string; uptime: number }> {
    return this.call({
      method: 'server.time',
      params: {},
    })
  }

  async getMetrics(): Promise<Record<string, unknown>> {
    return this.call({
      method: 'server.metrics',
      params: {},
    })
  }

  async updateServer(marketplaceUrl: string): Promise<'updating' | 'no-updates'> {
    return this.call({
      method: 'server.update',
      params: { 'marketplace-url': marketplaceUrl },
    })
  }

  async restartServer(): Promise<void> {
    return this.call({
      method: 'server.restart',
      params: {},
    })
  }

  async shutdownServer(): Promise<void> {
    return this.call({
      method: 'server.shutdown',
      params: {},
    })
  }

  async installPackage(id: string, marketplaceUrl: string, version: string): Promise<string> {
    return this.call({
      method: 'package.install',
      params: { id, 'marketplace-url': marketplaceUrl, version },
    })
  }

  async uninstallPackage(id: string): Promise<void> {
    return this.call({
      method: 'package.uninstall',
      params: { id },
    })
  }

  async startPackage(id: string): Promise<void> {
    return this.call({
      method: 'package.start',
      params: { id },
    })
  }

  async stopPackage(id: string): Promise<void> {
    return this.call({
      method: 'package.stop',
      params: { id },
    })
  }

  async restartPackage(id: string): Promise<void> {
    return this.call({
      method: 'package.restart',
      params: { id },
    })
  }

  async getMarketplace(url: string): Promise<Record<string, unknown>> {
    return this.call({
      method: 'marketplace.get',
      params: { url },
    })
  }

}

export const rpcClient = new RPCClient()

