// Package lightning — Alby Hub node implementation.
//
// Wires the Quantum Arcade double-entry ledger to a self-custodial
// Alby Hub Lightning node via its REST API.
//
// Alby Hub uses satoshis; the internal ledger uses millisatoshis.
// All conversions happen at this boundary.
package lightning

import (
	"bytes"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"time"
)

// AlbyNode implements Node against an Alby Hub instance.
type AlbyNode struct {
	baseURL string
	token   string
	client  *http.Client
}

// NewAlbyNode returns a Node backed by the Alby Hub at baseURL.
// token is the full-access JWT.
func NewAlbyNode(baseURL, token string) *AlbyNode {
	return &AlbyNode{
		baseURL: baseURL,
		token:   token,
		client: &http.Client{
			Timeout: 30 * time.Second,
		},
	}
}

// ───────── request helpers ─────────

func (a *AlbyNode) do(ctx context.Context, method, path string, body any) ([]byte, error) {
	var r io.Reader
	if body != nil {
		b, err := json.Marshal(body)
		if err != nil {
			return nil, err
		}
		r = bytes.NewReader(b)
	}
	req, err := http.NewRequestWithContext(ctx, method, a.baseURL+"/api/"+path, r)
	if err != nil {
		return nil, err
	}
	req.Header.Set("Authorization", "Bearer "+a.token)
	if body != nil {
		req.Header.Set("Content-Type", "application/json")
	}
	resp, err := a.client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("alby request: %w", err)
	}
	defer resp.Body.Close()
	data, err := io.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	if resp.StatusCode >= 400 {
		return nil, fmt.Errorf("alby %d: %s", resp.StatusCode, string(data[:min(len(data), 200)]))
	}
	return data, nil
}

func (a *AlbyNode) get(ctx context.Context, path string) ([]byte, error) {
	return a.do(ctx, "GET", path, nil)
}

func (a *AlbyNode) post(ctx context.Context, path string, body any) ([]byte, error) {
	return a.do(ctx, "POST", path, body)
}

// ───────── Node interface ─────────

type albyInvoice struct {
	PaymentHash string `json:"paymentHash"`
	Invoice     string `json:"invoice"`
	Amount      int64  `json:"amount"` // sats
	State       string `json:"state"`
	ExpiresAt   string `json:"expiresAt"`
}

// CreateInvoice creates a Lightning invoice via Alby Hub.
// Alby Hub uses sats; we convert to millisats for the ledger.
func (a *AlbyNode) CreateInvoice(ctx context.Context, amountMsat int64, memo string) (Invoice, error) {
	sats := msatToSat(amountMsat)
	if sats < 1 {
		sats = 1
	}
	raw, err := a.post(ctx, "invoices", map[string]any{
		"amount":      sats,
		"description": memo,
	})
	if err != nil {
		return Invoice{}, err
	}
	var inv albyInvoice
	if err := json.Unmarshal(raw, &inv); err != nil {
		return Invoice{}, fmt.Errorf("parsing alby invoice: %w", err)
	}
	expires, _ := time.Parse(time.RFC3339, inv.ExpiresAt)
	return Invoice{
		PaymentHash: inv.PaymentHash,
		Bolt11:      inv.Invoice,
		AmountMsat:  satToMsat(inv.Amount),
		ExpiresAt:   expires,
	}, nil
}

// LookupInvoice checks whether an invoice has been paid.
func (a *AlbyNode) LookupInvoice(ctx context.Context, paymentHash string) (bool, int64, error) {
	raw, err := a.get(ctx, "invoices/"+paymentHash)
	if err != nil {
		return false, 0, err
	}
	var inv albyInvoice
	if err := json.Unmarshal(raw, &inv); err != nil {
		return false, 0, fmt.Errorf("parsing alby invoice: %w", err)
	}
	return inv.State == "settled", satToMsat(inv.Amount), nil
}

type albyPayment struct {
	PaymentHash string `json:"paymentHash"`
	Preimage    string `json:"preimage"`
	Amount      int64  `json:"amountSat"`
	Fee         int64  `json:"feesPaidSat"`
	State       string `json:"state"`
}

// PayInvoice sends an outbound Lightning payment.
func (a *AlbyNode) PayInvoice(ctx context.Context, bolt11 string, maxFeeMsat int64) (Payment, error) {
	raw, err := a.post(ctx, "payments", map[string]string{
		"invoice": bolt11,
	})
	if err != nil {
		return Payment{}, err
	}
	var p albyPayment
	if err := json.Unmarshal(raw, &p); err != nil {
		return Payment{}, fmt.Errorf("parsing alby payment: %w", err)
	}
	if p.State != "settled" {
		return Payment{}, fmt.Errorf("payment %s: state=%s", p.PaymentHash, p.State)
	}
	return Payment{
		PaymentHash: p.PaymentHash,
		Preimage:    p.Preimage,
		AmountMsat:  satToMsat(p.Amount),
		FeeMsat:     satToMsat(p.Fee),
	}, nil
}

type albyBalances struct {
	Lightning struct {
		TotalSpendable int64 `json:"totalSpendableSat"`
	} `json:"lightning"`
}

// Balance returns the spendable Lightning balance in millisatoshis.
func (a *AlbyNode) Balance(ctx context.Context) (int64, error) {
	raw, err := a.get(ctx, "balances")
	if err != nil {
		return 0, err
	}
	var b albyBalances
	if err := json.Unmarshal(raw, &b); err != nil {
		return 0, fmt.Errorf("parsing alby balances: %w", err)
	}
	return satToMsat(b.Lightning.TotalSpendable), nil
}

// ───────── sat ↔ msat conversion ─────────

func satToMsat(sats int64) int64  { return sats * 1000 }
func msatToSat(msat int64) int64 { return msat / 1000 }
