/**
 * JSON-RPC client for Synor node communication.
 */

export interface RpcConfig {
  endpoint: string;
  timeout?: number;
}

export interface RpcResponse<T> {
  jsonrpc: '2.0';
  id: number;
  result?: T;
  error?: {
    code: number;
    message: string;
    data?: unknown;
  };
}

export interface NodeInfo {
  version: string;
  protocolVersion: number;
  peerCount: number;
  blockCount: number;
  blueScore: number;
  mempoolSize: number;
  synced: boolean;
}

export interface Balance {
  address: string;
  confirmed: string;
  pending: string;
  total: string;
}

export interface Transaction {
  id: string;
  inputs: TxInput[];
  outputs: TxOutput[];
  timestamp: number;
  blockHash?: string;
  confirmations: number;
}

export interface TxInput {
  previousTxId: string;
  outputIndex: number;
  signature: string;
}

export interface TxOutput {
  address: string;
  amount: string;
}

export interface SubmitTxResult {
  transactionId: string;
}

let rpcId = 0;

export class SynorRpcClient {
  private endpoint: string;
  private timeout: number;

  constructor(config: RpcConfig) {
    this.endpoint = config.endpoint;
    this.timeout = config.timeout ?? 30000;
  }

  /**
   * Make a JSON-RPC call.
   */
  private async call<T>(method: string, params: unknown[] = []): Promise<T> {
    const id = ++rpcId;

    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), this.timeout);

    try {
      const response = await fetch(this.endpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          jsonrpc: '2.0',
          method,
          params,
          id,
        }),
        signal: controller.signal,
      });

      if (!response.ok) {
        throw new Error(`HTTP error: ${response.status}`);
      }

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

      if (data.error) {
        throw new RpcError(data.error.code, data.error.message, data.error.data);
      }

      return data.result as T;
    } finally {
      clearTimeout(timeoutId);
    }
  }

  /**
   * Get node information.
   */
  async getInfo(): Promise<NodeInfo> {
    return this.call<NodeInfo>('synor_getInfo');
  }

  /**
   * Get address balance.
   */
  async getBalance(address: string): Promise<Balance> {
    return this.call<Balance>('synor_getBalance', [address]);
  }

  /**
   * Get transaction by ID.
   */
  async getTransaction(txId: string): Promise<Transaction> {
    return this.call<Transaction>('synor_getTransaction', [txId]);
  }

  /**
   * Get transactions for an address.
   */
  async getAddressTransactions(
    address: string,
    limit: number = 50,
    offset: number = 0
  ): Promise<Transaction[]> {
    return this.call<Transaction[]>('synor_getAddressTransactions', [
      address,
      limit,
      offset,
    ]);
  }

  /**
   * Get UTXOs for an address.
   */
  async getUtxos(address: string): Promise<Utxo[]> {
    return this.call<Utxo[]>('synor_getUtxos', [address]);
  }

  /**
   * Submit a signed transaction.
   */
  async submitTransaction(txHex: string): Promise<SubmitTxResult> {
    return this.call<SubmitTxResult>('synor_submitTransaction', [txHex]);
  }

  /**
   * Estimate transaction fee.
   */
  async estimateFee(numInputs: number, numOutputs: number): Promise<string> {
    return this.call<string>('synor_estimateFee', [numInputs, numOutputs]);
  }

  /**
   * Get current block tips.
   */
  async getTips(): Promise<string[]> {
    return this.call<string[]>('synor_getTips');
  }

  /**
   * Get mempool size.
   */
  async getMempoolSize(): Promise<number> {
    return this.call<number>('synor_getMempoolSize');
  }
}

export interface Utxo {
  txId: string;
  outputIndex: number;
  address: string;
  amount: string;
  blockHash?: string;
  confirmations: number;
}

export class RpcError extends Error {
  code: number;
  data?: unknown;

  constructor(code: number, message: string, data?: unknown) {
    super(message);
    this.name = 'RpcError';
    this.code = code;
    this.data = data;
  }
}

// Default client instance (can be reconfigured)
let defaultClient: SynorRpcClient | null = null;

export function getClient(): SynorRpcClient {
  if (!defaultClient) {
    // Use proxy in dev mode, direct endpoint in production
    const endpoint =
      import.meta.env.MODE === 'development'
        ? '/rpc'
        : import.meta.env.VITE_RPC_ENDPOINT || 'http://localhost:16110';

    defaultClient = new SynorRpcClient({ endpoint });
  }
  return defaultClient;
}

export function setClient(client: SynorRpcClient): void {
  defaultClient = client;
}
