-- SPDX-License-Identifier: AGPL-3.0-or-later

-- ─── org_billing_states ────────────────────────────────────────────

-- name: GetOrgBillingState :one
SELECT * FROM org_billing_states WHERE org_id = $1;

-- name: SetStripeCustomer :one
INSERT INTO org_billing_states (org_id, provider, stripe_customer_id)
VALUES ($1, 'stripe', $2)
ON CONFLICT (org_id) DO UPDATE
   SET stripe_customer_id = EXCLUDED.stripe_customer_id,
       provider = 'stripe',
       updated_at = now()
RETURNING *;

-- name: ApplySubscriptionSnapshot :one
WITH state AS (
    INSERT INTO org_billing_states (
        org_id,
        provider,
        plan,
        subscription_status,
        stripe_subscription_id,
        stripe_subscription_item_id,
        current_period_start,
        current_period_end,
        cancel_at_period_end,
        trial_end,
        canceled_at,
        last_webhook_event_id,
        past_due_at,
        locked_at,
        lock_reason,
        grace_until
    )
    VALUES (
        sqlc.arg(org_id)::bigint,
        'stripe',
        sqlc.arg(plan)::org_plan,
        sqlc.arg(subscription_status)::billing_subscription_status,
        sqlc.narg(stripe_subscription_id)::text,
        sqlc.narg(stripe_subscription_item_id)::text,
        sqlc.narg(current_period_start)::timestamptz,
        sqlc.narg(current_period_end)::timestamptz,
        sqlc.arg(cancel_at_period_end)::boolean,
        sqlc.narg(trial_end)::timestamptz,
        sqlc.narg(canceled_at)::timestamptz,
        sqlc.arg(last_webhook_event_id)::text,
        CASE
            WHEN sqlc.arg(subscription_status)::billing_subscription_status = 'past_due' THEN now()
            ELSE NULL
        END,
        NULL,
        NULL,
        NULL
    )
    ON CONFLICT (org_id) DO UPDATE
       SET plan = EXCLUDED.plan,
           subscription_status = EXCLUDED.subscription_status,
           stripe_subscription_id = EXCLUDED.stripe_subscription_id,
           stripe_subscription_item_id = EXCLUDED.stripe_subscription_item_id,
           current_period_start = EXCLUDED.current_period_start,
           current_period_end = EXCLUDED.current_period_end,
           cancel_at_period_end = EXCLUDED.cancel_at_period_end,
           trial_end = EXCLUDED.trial_end,
           canceled_at = EXCLUDED.canceled_at,
           last_webhook_event_id = EXCLUDED.last_webhook_event_id,
           past_due_at = CASE
               WHEN EXCLUDED.subscription_status = 'past_due' THEN COALESCE(org_billing_states.past_due_at, now())
               ELSE NULL
           END,
           locked_at = NULL,
           lock_reason = NULL,
           grace_until = NULL,
           updated_at = now()
    RETURNING *
), org_update AS (
    UPDATE orgs
       SET plan = sqlc.arg(plan)::org_plan,
           updated_at = now()
     WHERE id = sqlc.arg(org_id)::bigint
    RETURNING id
)
SELECT * FROM state;

-- name: MarkPastDue :one
UPDATE org_billing_states
   SET subscription_status = 'past_due',
       past_due_at = COALESCE(past_due_at, now()),
       locked_at = now(),
       lock_reason = 'past_due',
       grace_until = sqlc.narg(grace_until)::timestamptz,
       last_webhook_event_id = sqlc.arg(last_webhook_event_id)::text,
       updated_at = now()
 WHERE org_id = sqlc.arg(org_id)::bigint
RETURNING *;

-- name: MarkCanceled :one
WITH state AS (
    UPDATE org_billing_states
       SET plan = 'free',
           subscription_status = 'canceled',
           canceled_at = COALESCE(canceled_at, now()),
           locked_at = now(),
           lock_reason = 'canceled',
           grace_until = NULL,
           cancel_at_period_end = false,
           last_webhook_event_id = sqlc.arg(last_webhook_event_id)::text,
           updated_at = now()
     WHERE org_id = sqlc.arg(org_id)::bigint
    RETURNING *
), org_update AS (
    UPDATE orgs
       SET plan = 'free',
           updated_at = now()
     WHERE id = sqlc.arg(org_id)::bigint
    RETURNING id
)
SELECT * FROM state;

-- name: ClearBillingLock :one
WITH state AS (
    UPDATE org_billing_states
       SET plan = CASE
               WHEN subscription_status = 'canceled' THEN 'free'
               ELSE plan
           END,
           subscription_status = CASE
               WHEN subscription_status = 'canceled' THEN 'none'
               ELSE subscription_status
           END,
           locked_at = NULL,
           lock_reason = NULL,
           grace_until = NULL,
           updated_at = now()
     WHERE org_id = $1
    RETURNING *
), org_update AS (
    UPDATE orgs
       SET plan = state.plan,
           updated_at = now()
      FROM state
     WHERE orgs.id = state.org_id
    RETURNING orgs.id
)
SELECT * FROM state;

-- ─── billing_seat_snapshots ────────────────────────────────────────

-- name: CreateSeatSnapshot :one
WITH snapshot AS (
    INSERT INTO billing_seat_snapshots (
        org_id,
        provider,
        stripe_subscription_id,
        active_members,
        billable_seats,
        source
    )
    VALUES (
        sqlc.arg(org_id)::bigint,
        'stripe',
        sqlc.narg(stripe_subscription_id)::text,
        sqlc.arg(active_members)::integer,
        sqlc.arg(billable_seats)::integer,
        sqlc.arg(source)::text
    )
    RETURNING *
), state AS (
    INSERT INTO org_billing_states (org_id, billable_seats, seat_snapshot_at)
    SELECT org_id, billable_seats, captured_at FROM snapshot
    ON CONFLICT (org_id) DO UPDATE
       SET billable_seats = EXCLUDED.billable_seats,
           seat_snapshot_at = EXCLUDED.seat_snapshot_at,
           updated_at = now()
    RETURNING org_id
)
SELECT * FROM snapshot;

-- name: ListSeatSnapshotsForOrg :many
SELECT * FROM billing_seat_snapshots
WHERE org_id = $1
ORDER BY captured_at DESC, id DESC
LIMIT $2;

-- ─── billing_invoices ──────────────────────────────────────────────

-- name: UpsertInvoice :one
INSERT INTO billing_invoices (
    org_id,
    provider,
    stripe_invoice_id,
    stripe_customer_id,
    stripe_subscription_id,
    status,
    number,
    currency,
    amount_due_cents,
    amount_paid_cents,
    amount_remaining_cents,
    hosted_invoice_url,
    invoice_pdf_url,
    period_start,
    period_end,
    due_at,
    paid_at,
    voided_at
)
VALUES (
    sqlc.arg(org_id)::bigint,
    'stripe',
    sqlc.arg(stripe_invoice_id)::text,
    sqlc.arg(stripe_customer_id)::text,
    sqlc.narg(stripe_subscription_id)::text,
    sqlc.arg(status)::billing_invoice_status,
    sqlc.arg(number)::text,
    sqlc.arg(currency)::text,
    sqlc.arg(amount_due_cents)::bigint,
    sqlc.arg(amount_paid_cents)::bigint,
    sqlc.arg(amount_remaining_cents)::bigint,
    sqlc.arg(hosted_invoice_url)::text,
    sqlc.arg(invoice_pdf_url)::text,
    sqlc.narg(period_start)::timestamptz,
    sqlc.narg(period_end)::timestamptz,
    sqlc.narg(due_at)::timestamptz,
    sqlc.narg(paid_at)::timestamptz,
    sqlc.narg(voided_at)::timestamptz
)
ON CONFLICT (provider, stripe_invoice_id) DO UPDATE
   SET org_id = EXCLUDED.org_id,
       stripe_customer_id = EXCLUDED.stripe_customer_id,
       stripe_subscription_id = EXCLUDED.stripe_subscription_id,
       status = EXCLUDED.status,
       number = EXCLUDED.number,
       currency = EXCLUDED.currency,
       amount_due_cents = EXCLUDED.amount_due_cents,
       amount_paid_cents = EXCLUDED.amount_paid_cents,
       amount_remaining_cents = EXCLUDED.amount_remaining_cents,
       hosted_invoice_url = EXCLUDED.hosted_invoice_url,
       invoice_pdf_url = EXCLUDED.invoice_pdf_url,
       period_start = EXCLUDED.period_start,
       period_end = EXCLUDED.period_end,
       due_at = EXCLUDED.due_at,
       paid_at = EXCLUDED.paid_at,
       voided_at = EXCLUDED.voided_at,
       updated_at = now()
RETURNING *;

-- name: ListInvoicesForOrg :many
SELECT * FROM billing_invoices
WHERE org_id = $1
ORDER BY created_at DESC, id DESC
LIMIT $2;

-- ─── billing_webhook_events ────────────────────────────────────────

-- name: CreateWebhookEventReceipt :one
INSERT INTO billing_webhook_events (
    provider,
    provider_event_id,
    event_type,
    api_version,
    payload
)
VALUES (
    'stripe',
    sqlc.arg(provider_event_id)::text,
    sqlc.arg(event_type)::text,
    sqlc.arg(api_version)::text,
    sqlc.arg(payload)::jsonb
)
ON CONFLICT (provider, provider_event_id) DO NOTHING
RETURNING *;

-- name: MarkWebhookEventProcessed :one
UPDATE billing_webhook_events
   SET processed_at = now(),
       process_error = '',
       processing_attempts = processing_attempts + 1
 WHERE provider = 'stripe'
   AND provider_event_id = $1
RETURNING *;

-- name: MarkWebhookEventFailed :one
UPDATE billing_webhook_events
   SET process_error = $2,
       processing_attempts = processing_attempts + 1
 WHERE provider = 'stripe'
   AND provider_event_id = $1
RETURNING *;
