import type { BitcoinNetwork } from './types';
import { getServiceCredentials } from 'officerdb';
import { getKey } from 'officerdb/secret-store';

// The ONLY reader of WALLET_* env in the tree. Everything else — node URLs, macaroons, runes, LNDHub
// credentials, NWC URIs — is per-wallet configuration the owner enters at runtime and lives encrypted in
// Postgres (databases/officer_db/src/wallet/schema.ts), not here. Env holds only what is genuinely
// deployment-wide: which chain we're on.
//
// WHERE CHAIN DATA COMES FROM IS NOT ENV. It used to be WALLET_ESPLORA_URL, which meant the one setting
// an owner actually has to change — off the public explorer, onto their own indexer — was the one
// setting they could only change with a shell and a restart. It now lives in `service_connections`
// under 'esplora' and is edited from Wallet → Settings; see getChainSource below.

const NETWORKS: readonly BitcoinNetwork[] = ['bitcoin', 'testnet', 'signet', 'regtest'];

// mempool.space's public API. Fine to start on; swap it for your own electrs/esplora when the node is up
// — an Esplora endpoint sees every address in the wallet, so the public one is a privacy leak, not a
// custody one. It never sees a private key and cannot authorize anything.
const DEFAULT_ESPLORA: Record<BitcoinNetwork, string> = {
  bitcoin: 'https://mempool.space/api',
  testnet: 'https://mempool.space/testnet/api',
  signet: 'https://mempool.space/signet/api',
  regtest: 'http://127.0.0.1:3002',
};

export type WalletConfig = {
  network: BitcoinNetwork;
  esploraUrl: string;
  unlockTtlSec: number;
};

let warned = false;

export function getConfig(): WalletConfig {
  const raw = process.env.WALLET_NETWORK?.trim() ?? 'bitcoin';
  const network = (NETWORKS as readonly string[]).includes(raw) ? (raw as BitcoinNetwork) : 'bitcoin';
  if (raw && network !== raw && !warned) {
    console.warn(`[wallet] WALLET_NETWORK="${raw}" is not a known network, falling back to bitcoin`);
    warned = true;
  }

  const ttl = Number(process.env.WALLET_UNLOCK_TTL_SEC ?? 900);

  return {
    network,
    /** The built-in default, used until the owner stores one. `getChainSource` is the real answer. */
    esploraUrl: DEFAULT_ESPLORA[network],
    // Clamp: a zero TTL makes the wallet unusable, and an unbounded one defeats auto-lock entirely.
    unlockTtlSec: Number.isFinite(ttl) ? Math.min(Math.max(ttl, 30), 86_400) : 900,
  };
}

/**
 * Which protocol the endpoint speaks. Not interchangeable — see chain-source.ts for why the two are
 * different shapes rather than two URLs for the same thing.
 */
export const CHAIN_KINDS = ['nbxplorer', 'esplora'] as const;

export type ChainKind = (typeof CHAIN_KINDS)[number];

export type ChainSource = {
  kind: ChainKind;
  /** API root, no trailing slash. */
  baseUrl: string;
  /** True when this is the owner's stored endpoint rather than the built-in public default. */
  custom: boolean;
};

/** Short TTL: a save invalidates explicitly, so this only bounds how stale a *concurrent* reader can be. */
const CHAIN_TTL_MS = 60_000;
const chainCache = new Map<number, { source: ChainSource; at: number }>();

export const normalizeEsploraUrl = (url: string): string => url.trim().replace(/\/+$/, '');

/**
 * Where to read the chain, for this owner.
 *
 * AT MOST ONE of the two rows exists: saving either kind retires the other (config.ts::save), because a
 * wallet reads from one place and storing two would leave the answer to precedence rather than to the
 * owner. The order below is therefore a tiebreak that should never be needed, not a preference.
 *
 * Falls back to the public default rather than failing: a wallet with no stored endpoint is still a
 * working watch-only wallet, just one pointed at an explorer that rate-limits and can see every address
 * it asks about. The default is a starting point, not a destination.
 */
export async function getChainSource(userId: number): Promise<ChainSource> {
  const hit = chainCache.get(userId);
  if (hit && Date.now() - hit.at < CHAIN_TTL_MS) return hit.source;

  const source = await readChainSource(userId);
  chainCache.set(userId, { source, at: Date.now() });
  return source;
}

async function readChainSource(userId: number): Promise<ChainSource> {
  for (const kind of CHAIN_KINDS) {
    const creds = await getServiceCredentials(userId, kind);
    if (creds?.url) return { kind, baseUrl: normalizeEsploraUrl(creds.url), custom: true };
  }
  return { kind: 'esplora', baseUrl: getConfig().esploraUrl, custom: false };
}

export function invalidateChainSource(userId: number): void {
  chainCache.delete(userId);
}

/**
 * Whether the wallet's at-rest key is usable. The sidecar can serve a locked, watch-only view without
 * one, but every write path touching an encrypted column throws, so /_health reports it explicitly
 * rather than letting the first wallet creation fail with a confusing crypto error.
 *
 * This tested `VAULT_STORE_KEY` in the environment until 2026-08-13. The key now lives in the secret
 * store under the `wallet` purpose and is CREATED ON FIRST USE — so the honest question is no longer
 * "did somebody set a variable" but "can this process open the store at all". A false here means the
 * file is unreachable or unwritable, which is an install problem rather than a configuration one.
 */
export function hasStoreKey(): boolean {
  try {
    return getKey('wallet').length > 0;
  } catch {
    return false;
  }
}
