/* ─────────────────────────────────────────────
   fetch queries
   ─────────────────────────────────────────────
*/

-- name: FilterPayments :many
SELECT
    sqlc.embed(p),
    i.intent_type AS "intent_type",
    i.intent_payload AS "intent_payload"
FROM payments p
LEFT JOIN payment_intents i ON i.payment_id = p.id
WHERE p.id > COALESCE(sqlc.narg('index_offset_get'), -1)
  AND p.id < COALESCE(sqlc.narg('index_offset_let'), 9223372036854775807)
  -- NOTE: We use non-nullable time params with Go-side defaults instead of
  -- COALESCE, because COALESCE with text fallback causes type mismatch on
  -- Postgres (timestamp vs text), and OR-based optional filters can prevent
  -- the planner from using the created_at index.
  AND p.created_at >= @created_after
  AND p.created_at <= @created_before
  AND (
      i.intent_type = sqlc.narg('intent_type') OR
      sqlc.narg('intent_type') IS NULL OR i.intent_type IS NULL
  )
ORDER BY p.id ASC
LIMIT @num_limit;

-- name: FilterPaymentsDesc :many
SELECT
    sqlc.embed(p),
    i.intent_type AS "intent_type",
    i.intent_payload AS "intent_payload"
FROM payments p
LEFT JOIN payment_intents i ON i.payment_id = p.id
WHERE p.id > COALESCE(sqlc.narg('index_offset_get'), -1)
  AND p.id < COALESCE(sqlc.narg('index_offset_let'), 9223372036854775807)
  -- NOTE: We use non-nullable time params with Go-side defaults instead of
  -- COALESCE, because COALESCE with text fallback causes type mismatch on
  -- Postgres (timestamp vs text), and OR-based optional filters can prevent
  -- the planner from using the created_at index.
  AND p.created_at >= @created_after
  AND p.created_at <= @created_before
  AND (
      i.intent_type = sqlc.narg('intent_type') OR
      sqlc.narg('intent_type') IS NULL OR i.intent_type IS NULL
  )
ORDER BY p.id DESC
LIMIT @num_limit;

-- name: FetchPayment :one
SELECT
    sqlc.embed(p),
    i.intent_type AS "intent_type",
    i.intent_payload AS "intent_payload"
FROM payments p
LEFT JOIN payment_intents i ON i.payment_id = p.id
WHERE p.payment_identifier = $1;

-- name: FetchPaymentDuplicates :many
-- Fetch all duplicate payment records from the payment_duplicates table for
-- a given payment ID.
SELECT
    id,
    payment_id,
    amount_msat,
    created_at,
    fail_reason,
    settle_preimage,
    settle_time
FROM payment_duplicates
WHERE payment_id = $1
ORDER BY id ASC;

-- name: CountPayments :one
SELECT COUNT(*) FROM payments;

-- name: FetchHtlcAttemptsForPayments :many
SELECT
    ha.id,
    ha.attempt_index,
    ha.payment_id,
    ha.session_key,
    ha.attempt_time,
    ha.payment_hash,
    ha.first_hop_amount_msat,
    ha.route_total_time_lock,
    ha.route_total_amount,
    ha.route_source_key,
    hr.resolution_type,
    hr.resolution_time,
    hr.failure_source_index,
    hr.htlc_fail_reason,
    hr.failure_msg,
    hr.settle_preimage
FROM payment_htlc_attempts ha
LEFT JOIN payment_htlc_attempt_resolutions hr ON hr.attempt_index = ha.attempt_index
WHERE ha.payment_id IN (sqlc.slice('payment_ids')/*SLICE:payment_ids*/)
ORDER BY ha.payment_id ASC, ha.attempt_time ASC;

-- name: FetchHtlcAttemptResolutionsForPayments :many
-- Batch query to fetch only HTLC resolution status for multiple payments.
-- We don't need to order by payment_id and attempt_time because we will
-- group the resolutions by payment_id in the background.
SELECT
    ha.payment_id,
    hr.resolution_type
FROM payment_htlc_attempts ha
LEFT JOIN payment_htlc_attempt_resolutions hr ON hr.attempt_index = ha.attempt_index
WHERE ha.payment_id IN (sqlc.slice('payment_ids')/*SLICE:payment_ids*/);

-- name: FetchPaymentsByIDs :many
-- Batch fetch payment and intent data for a set of payment IDs.
-- Used to avoid fetching redundant payment data when processing multiple
-- attempts for the same payment.
SELECT
    p.id,
    p.amount_msat,
    p.created_at,
    p.payment_identifier,
    p.fail_reason,
    pi.intent_type,
    pi.intent_payload
FROM payments p
LEFT JOIN payment_intents pi ON pi.payment_id = p.id
WHERE p.id IN (sqlc.slice('payment_ids')/*SLICE:payment_ids*/)
ORDER BY p.id ASC;

-- name: FetchNonTerminalPayments :many
-- Fetch all non-terminal payments using pagination. A payment is
-- non-terminal if it has an unresolved attempt, or if it has not been
-- permanently failed and has no settled attempt yet.
SELECT
    p.id,
    p.amount_msat,
    p.created_at,
    p.payment_identifier,
    p.fail_reason,
    pi.intent_type,
    pi.intent_payload
FROM payments p
LEFT JOIN payment_intents pi
    ON pi.payment_id = p.id
WHERE p.id > $1
AND (
    (
        p.fail_reason IS NULL
        AND NOT EXISTS (
            SELECT 1
            FROM payment_htlc_attempts ha
            JOIN payment_htlc_attempt_resolutions hr
                ON hr.attempt_index = ha.attempt_index
            WHERE ha.payment_id = p.id
            AND hr.resolution_type = 1
        )
    )
    OR EXISTS (
        SELECT 1
        FROM payment_htlc_attempts ha
        WHERE ha.payment_id = p.id
        AND NOT EXISTS (
            SELECT 1
            FROM payment_htlc_attempt_resolutions hr
            WHERE hr.attempt_index = ha.attempt_index
        )
    )
)
ORDER BY p.id ASC
LIMIT $2;

-- name: FetchHopsForAttempts :many
SELECT
    h.id,
    h.htlc_attempt_index,
    h.hop_index,
    h.pub_key,
    h.scid,
    h.outgoing_time_lock,
    h.amt_to_forward,
    h.meta_data,
    m.payment_addr AS mpp_payment_addr,
    m.total_msat AS mpp_total_msat,
    a.root_share AS amp_root_share,
    a.set_id AS amp_set_id,
    a.child_index AS amp_child_index,
    b.encrypted_data,
    b.blinding_point,
    b.blinded_path_total_amt
FROM payment_route_hops h
LEFT JOIN payment_route_hop_mpp m ON m.hop_id = h.id
LEFT JOIN payment_route_hop_amp a ON a.hop_id = h.id
LEFT JOIN payment_route_hop_blinded b ON b.hop_id = h.id
WHERE h.htlc_attempt_index IN (sqlc.slice('htlc_attempt_indices')/*SLICE:htlc_attempt_indices*/)
ORDER BY h.htlc_attempt_index ASC, h.hop_index ASC;


-- name: FetchPaymentLevelFirstHopCustomRecords :many
SELECT
    l.id,
    l.payment_id,
    l.key,
    l.value
FROM payment_first_hop_custom_records l
WHERE l.payment_id IN (sqlc.slice('payment_ids')/*SLICE:payment_ids*/)
ORDER BY l.payment_id ASC, l.key ASC;

-- name: FetchRouteLevelFirstHopCustomRecords :many
SELECT
    l.id,
    l.htlc_attempt_index,
    l.key,
    l.value
FROM payment_attempt_first_hop_custom_records l
WHERE l.htlc_attempt_index IN (sqlc.slice('htlc_attempt_indices')/*SLICE:htlc_attempt_indices*/)
ORDER BY l.htlc_attempt_index ASC, l.key ASC;

-- name: FetchHopLevelCustomRecords :many
SELECT
    l.id,
    l.hop_id,
    l.key,
    l.value
FROM payment_hop_custom_records l
WHERE l.hop_id IN (sqlc.slice('hop_ids')/*SLICE:hop_ids*/)
ORDER BY l.hop_id ASC, l.key ASC;


-- name: DeletePayment :exec
DELETE FROM payments WHERE id = $1;

-- name: DeleteFailedAttempts :exec
-- Delete all failed HTLC attempts for the given payment. Resolution type 2
-- indicates a failed attempt. Uses EXISTS to scope the resolution lookup to
-- only this payment's attempts, avoiding an O(N) scan of all failed
-- resolutions across all payments.
DELETE FROM payment_htlc_attempts
WHERE payment_id = $1
AND EXISTS (
    SELECT 1 FROM payment_htlc_attempt_resolutions hr
    WHERE hr.attempt_index = payment_htlc_attempts.attempt_index
    AND hr.resolution_type = 2
);

-- name: InsertPaymentIntent :one
-- Insert a payment intent for a given payment and return its ID.
INSERT INTO payment_intents (
    payment_id,
    intent_type, 
    intent_payload)
VALUES (
    @payment_id,
    @intent_type, 
    @intent_payload
)
RETURNING id;

-- name: InsertPayment :one
-- Insert a new payment and return its ID.
-- When creating a payment we don't have a fail reason because we start the
-- payment process.
INSERT INTO payments (
    amount_msat, 
    created_at, 
    payment_identifier,
    fail_reason)
VALUES (
    @amount_msat,
    @created_at,
    @payment_identifier,
    NULL
)
RETURNING id;

-- name: InsertPaymentFirstHopCustomRecord :exec
INSERT INTO payment_first_hop_custom_records (
    payment_id,
    key,
    value
)
VALUES (
    @payment_id,
    @key,
    @value
);

-- name: InsertHtlcAttempt :one
INSERT INTO payment_htlc_attempts (
    payment_id,
    attempt_index,
    session_key,
    attempt_time,
    payment_hash,
    first_hop_amount_msat,
    route_total_time_lock,
    route_total_amount,
    route_source_key)
VALUES (
    @payment_id,
    @attempt_index,
    @session_key,
    @attempt_time,
    @payment_hash, 
    @first_hop_amount_msat, 
    @route_total_time_lock, 
    @route_total_amount, 
    @route_source_key)
RETURNING id;

-- name: InsertPaymentAttemptFirstHopCustomRecord :exec
INSERT INTO payment_attempt_first_hop_custom_records (
    htlc_attempt_index,
    key,
    value
)
VALUES (
    @htlc_attempt_index,
    @key,
    @value
);

-- name: InsertRouteHop :one
INSERT INTO payment_route_hops (
    htlc_attempt_index,
    hop_index,
    pub_key,
    scid,
    outgoing_time_lock,
    amt_to_forward,
    meta_data
)
VALUES (
    @htlc_attempt_index,
    @hop_index,
    @pub_key,
    @scid,
    @outgoing_time_lock,
    @amt_to_forward,
    @meta_data
)
RETURNING id;

-- name: InsertRouteHopMpp :exec
INSERT INTO payment_route_hop_mpp (
    hop_id,
    payment_addr,
    total_msat
)
VALUES (
    @hop_id,
    @payment_addr,
    @total_msat
);

-- name: InsertRouteHopAmp :exec
INSERT INTO payment_route_hop_amp (
    hop_id,
    root_share,
    set_id,
    child_index
)
VALUES (
    @hop_id,
    @root_share,
    @set_id,
    @child_index
);

-- name: InsertRouteHopBlinded :exec
INSERT INTO payment_route_hop_blinded (
    hop_id,
    encrypted_data,
    blinding_point,
    blinded_path_total_amt
)
VALUES (
    @hop_id,
    @encrypted_data,
    @blinding_point,
    @blinded_path_total_amt
);

-- name: InsertPaymentHopCustomRecord :exec
INSERT INTO payment_hop_custom_records (
    hop_id,
    key,
    value
)
VALUES (
    @hop_id,
    @key,
    @value
);

-- name: SettleAttempt :exec
INSERT INTO payment_htlc_attempt_resolutions (
    attempt_index,
    resolution_time,
    resolution_type,
    settle_preimage
)
VALUES (
    @attempt_index,
    @resolution_time,
    @resolution_type,
    @settle_preimage
);

-- name: FailAttempt :exec
INSERT INTO payment_htlc_attempt_resolutions (
    attempt_index,
    resolution_time,
    resolution_type,
    failure_source_index,
    htlc_fail_reason,
    failure_msg
)
VALUES (
    @attempt_index,
    @resolution_time,
    @resolution_type,
    @failure_source_index,
    @htlc_fail_reason,
    @failure_msg
);

-- name: FailPayment :execresult
UPDATE payments SET fail_reason = $1 WHERE payment_identifier = $2;

/* ─────────────────────────────────────────────
   Migration-specific queries

   These queries are used ONLY for the one-time migration from KV to SQL.
   ─────────────────────────────────────────────
*/

-- name: InsertPaymentMig :one
-- Migration-specific payment insert that allows setting fail_reason.
-- Normal InsertPayment forces fail_reason to NULL since new payments
-- aren't failed yet. During migration, we're inserting historical data
-- that may already be failed.
INSERT INTO payments (
    amount_msat,
    created_at,
    payment_identifier,
    fail_reason)
VALUES (
    @amount_msat,
    @created_at,
    @payment_identifier,
    @fail_reason
)
RETURNING id;

-- name: FetchPaymentsByIDsMig :many
-- Migration-specific batch fetch that returns payment data along with HTLC
-- attempt counts for structural validation during KV to SQL migration.
SELECT
    p.id,
    p.amount_msat,
    p.created_at,
    p.payment_identifier,
    p.fail_reason,
    COUNT(ha.id) AS htlc_attempt_count
FROM payments p
LEFT JOIN payment_htlc_attempts ha ON ha.payment_id = p.id
WHERE p.id IN (sqlc.slice('payment_ids')/*SLICE:payment_ids*/)
GROUP BY p.id, p.amount_msat, p.created_at, p.payment_identifier, p.fail_reason
ORDER BY p.id ASC;

-- name: InsertPaymentDuplicateMig :one
-- Insert a duplicate payment record into the payment_duplicates table and 
-- return its ID.
INSERT INTO payment_duplicates (
    payment_id,
    amount_msat,
    created_at,
    fail_reason,
    settle_preimage,
    settle_time
)
VALUES (
    @payment_id,
    @amount_msat,
    @created_at,
    @fail_reason,
    @settle_preimage,
    @settle_time
)
RETURNING id;
