import { randomBytes } from "crypto";
import { makeLogger } from "./logger.js";

const logger = makeLogger("lnbits");

export interface LNbitsInvoice {
  paymentHash: string;
  paymentRequest: string;
}

export interface LNbitsConfig {
  url: string;
  apiKey: string;
}

const stubPaidInvoices = new Set<string>();

export class LNbitsService {
  private readonly url: string;
  private readonly apiKey: string;
  readonly stubMode: boolean;

  constructor(config?: Partial<LNbitsConfig>) {
    this.url = config?.url ?? process.env.LNBITS_URL ?? "";
    this.apiKey = config?.apiKey ?? process.env.LNBITS_API_KEY ?? "";
    this.stubMode = !this.url || !this.apiKey;
    if (this.stubMode) {
      logger.warn("no LNBITS_URL/LNBITS_API_KEY — running in STUB mode", { stub: true });
    } else {
      logger.info("LNbits real mode active", { url: this.url, stub: false });
    }
  }

  // ── Inbound invoices ─────────────────────────────────────────────────────

  async createInvoice(amountSats: number, memo: string): Promise<LNbitsInvoice> {
    if (this.stubMode) {
      const paymentHash = randomBytes(32).toString("hex");
      const paymentRequest = `lnbcrt${amountSats}u1stub_${paymentHash.slice(0, 16)}`;
      logger.info("stub invoice created", { amountSats, memo, paymentHash });
      return { paymentHash, paymentRequest };
    }

    const response = await fetch(`${this.base()}/api/v1/payments`, {
      method: "POST",
      headers: this.headers(),
      body: JSON.stringify({ out: false, amount: amountSats, memo }),
    });

    if (!response.ok) {
      const body = await response.text();
      throw new Error(`LNbits createInvoice failed (${response.status}): ${body}`);
    }

    const data = (await response.json()) as {
      payment_hash: string;
      payment_request: string;
    };
    return { paymentHash: data.payment_hash, paymentRequest: data.payment_request };
  }

  async checkInvoicePaid(paymentHash: string): Promise<boolean> {
    if (this.stubMode) {
      return stubPaidInvoices.has(paymentHash);
    }

    const response = await fetch(
      `${this.base()}/api/v1/payments/${paymentHash}`,
      { method: "GET", headers: this.headers() },
    );

    if (!response.ok) {
      const body = await response.text();
      throw new Error(`LNbits checkInvoice failed (${response.status}): ${body}`);
    }

    const data = (await response.json()) as { paid: boolean };
    return data.paid;
  }

  // ── Outbound payments (refunds) ──────────────────────────────────────────

  /**
   * Decode a BOLT11 invoice and return its amount in satoshis.
   * Stub mode: parses the embedded amount from our known stub format (lnbcrtXu1stub_...),
   * or returns null for externally-generated invoices.
   */
  async decodeInvoice(bolt11: string): Promise<{ amountSats: number } | null> {
    if (this.stubMode) {
      // Our stub format: lnbcrtNNNu1stub_...  where NNN is the amount in sats
      const m = bolt11.match(/^lnbcrt(\d+)u1stub_/i);
      if (m) return { amountSats: parseInt(m[1], 10) };
      // Unknown format in stub mode — accept blindly (simulated environment)
      return null;
    }

    const response = await fetch(`${this.base()}/api/v1/payments/decode`, {
      method: "POST",
      headers: this.headers(),
      body: JSON.stringify({ data: bolt11 }),
    });

    if (!response.ok) {
      const body = await response.text();
      throw new Error(`LNbits decodeInvoice failed (${response.status}): ${body}`);
    }

    const data = (await response.json()) as { amount_msat?: number };
    if (!data.amount_msat) return null;
    return { amountSats: Math.floor(data.amount_msat / 1000) };
  }

  /**
   * Pay an outgoing BOLT11 invoice (e.g. to return a refund to a user).
   * Returns the payment hash.
   * Stub mode: simulates a successful payment.
   */
  async payInvoice(bolt11: string): Promise<string> {
    if (this.stubMode) {
      const paymentHash = randomBytes(32).toString("hex");
      logger.info("stub outgoing payment", { paymentHash, invoiceType: "outbound" });
      return paymentHash;
    }

    const response = await fetch(`${this.base()}/api/v1/payments`, {
      method: "POST",
      headers: this.headers(),
      body: JSON.stringify({ out: true, bolt11 }),
    });

    if (!response.ok) {
      const body = await response.text();
      throw new Error(`LNbits payInvoice failed (${response.status}): ${body}`);
    }

    const data = (await response.json()) as { payment_hash: string };
    return data.payment_hash;
  }

  // ── Stub helpers ─────────────────────────────────────────────────────────

  /** Stub-only: mark an inbound invoice as paid for testing/dev flows. */
  stubMarkPaid(paymentHash: string): void {
    if (!this.stubMode) {
      throw new Error("stubMarkPaid called on a real LNbitsService instance");
    }
    stubPaidInvoices.add(paymentHash);
    logger.info("stub invoice marked paid", { paymentHash, invoiceType: "inbound" });
  }

  // ── Private helpers ──────────────────────────────────────────────────────

  private base(): string {
    return this.url.replace(/\/$/, "");
  }

  private headers(): Record<string, string> {
    return {
      "Content-Type": "application/json",
      "X-Api-Key": this.apiKey,
    };
  }
}

export const lnbitsService = new LNbitsService();
