// Code generated by sqlc. DO NOT EDIT.
// versions:
//   sqlc v1.31.1
// source: diagnostics.sql

package dbq

import (
	"context"

	"github.com/jackc/pgx/v5/pgtype"
)

const gcPruneDiagnostics = `-- name: GcPruneDiagnostics :execrows
DELETE FROM diagnostic_events WHERE received_at < now() - INTERVAL '30 days'
`

// Retention sweep for the gc worker. Deletes diagnostic_events older
// than 30 days by server clock (received_at) so a skewed client clock
// can't keep rows alive. Matches the other gc lifecycle sweeps'
// hardcoded-threshold style; diagnostics are debug telemetry, not user
// data, and 30 days is ample to investigate a live incident after the
// fact. Returns the affected row count for the sweep log line.
func (q *Queries) GcPruneDiagnostics(ctx context.Context) (int64, error) {
	result, err := q.db.Exec(ctx, gcPruneDiagnostics)
	if err != nil {
		return 0, err
	}
	return result.RowsAffected(), nil
}

const insertDiagnosticEvent = `-- name: InsertDiagnosticEvent :one
INSERT INTO diagnostic_events (
    user_id, client_id, app_version, os_version, kind, payload, occurred_at
)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id, received_at
`

type InsertDiagnosticEventParams struct {
	UserID     pgtype.UUID
	ClientID   string
	AppVersion *string
	OsVersion  *string
	Kind       string
	Payload    []byte
	OccurredAt pgtype.Timestamptz
}

type InsertDiagnosticEventRow struct {
	ID         pgtype.UUID
	ReceivedAt pgtype.Timestamptz
}

// Records one client-reported diagnostic event. The writer validates the
// `kind` category against its whitelist before this runs; the CHECK
// constraint is the belt-and-braces guard. payload carries the finer
// event discriminator + all event-specific fields. Returns received_at
// so the caller can confirm ingest.
func (q *Queries) InsertDiagnosticEvent(ctx context.Context, arg InsertDiagnosticEventParams) (InsertDiagnosticEventRow, error) {
	row := q.db.QueryRow(ctx, insertDiagnosticEvent,
		arg.UserID,
		arg.ClientID,
		arg.AppVersion,
		arg.OsVersion,
		arg.Kind,
		arg.Payload,
		arg.OccurredAt,
	)
	var i InsertDiagnosticEventRow
	err := row.Scan(&i.ID, &i.ReceivedAt)
	return i, err
}

const listAdminDiagnostics = `-- name: ListAdminDiagnostics :many
SELECT
    de.id          AS id,
    de.user_id     AS user_id,
    u.username     AS username,
    de.client_id   AS client_id,
    de.app_version AS app_version,
    de.os_version  AS os_version,
    de.kind        AS kind,
    de.payload     AS payload,
    de.occurred_at AS occurred_at,
    de.received_at AS received_at
FROM diagnostic_events de
JOIN users u ON u.id = de.user_id
WHERE ($1::uuid   IS NULL OR de.user_id   = $1::uuid)
  AND ($2::text IS NULL OR de.client_id = $2::text)
  AND ($3::text      IS NULL OR de.kind      = $3::text)
  AND ($4::timestamptz IS NULL
       OR de.occurred_at >= $4::timestamptz)
  AND ($5::timestamptz IS NULL
       OR de.occurred_at <= $5::timestamptz)
ORDER BY de.occurred_at DESC
LIMIT $7::int OFFSET $6::int
`

type ListAdminDiagnosticsParams struct {
	UserID   pgtype.UUID
	ClientID *string
	Kind     *string
	FromTs   pgtype.Timestamptz
	ToTs     pgtype.Timestamptz
	Off      int32
	Lim      int32
}

type ListAdminDiagnosticsRow struct {
	ID         pgtype.UUID
	UserID     pgtype.UUID
	Username   string
	ClientID   string
	AppVersion *string
	OsVersion  *string
	Kind       string
	Payload    []byte
	OccurredAt pgtype.Timestamptz
	ReceivedAt pgtype.Timestamptz
}

// Admin timeline query. All filters are optional (NULL = no filter):
// account (user_id), device (client_id), category (kind), and the
// occurred_at window (from_ts/to_ts). Newest-first; the SPA reverses for
// a chronological timeline / export. Joined with users for the username
// so the view renders without a second round-trip.
func (q *Queries) ListAdminDiagnostics(ctx context.Context, arg ListAdminDiagnosticsParams) ([]ListAdminDiagnosticsRow, error) {
	rows, err := q.db.Query(ctx, listAdminDiagnostics,
		arg.UserID,
		arg.ClientID,
		arg.Kind,
		arg.FromTs,
		arg.ToTs,
		arg.Off,
		arg.Lim,
	)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []ListAdminDiagnosticsRow
	for rows.Next() {
		var i ListAdminDiagnosticsRow
		if err := rows.Scan(
			&i.ID,
			&i.UserID,
			&i.Username,
			&i.ClientID,
			&i.AppVersion,
			&i.OsVersion,
			&i.Kind,
			&i.Payload,
			&i.OccurredAt,
			&i.ReceivedAt,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}

const listDiagnosticDevices = `-- name: ListDiagnosticDevices :many
SELECT
    de.client_id            AS client_id,
    de.user_id              AS user_id,
    u.username              AS username,
    max(de.app_version)::text        AS app_version,
    max(de.os_version)::text         AS os_version,
    max(de.occurred_at)::timestamptz AS last_seen,
    count(*)                         AS event_count
FROM diagnostic_events de
JOIN users u ON u.id = de.user_id
WHERE ($1::uuid IS NULL OR de.user_id = $1::uuid)
GROUP BY de.client_id, de.user_id, u.username
ORDER BY last_seen DESC
`

type ListDiagnosticDevicesRow struct {
	ClientID   string
	UserID     pgtype.UUID
	Username   string
	AppVersion string
	OsVersion  string
	LastSeen   pgtype.Timestamptz
	EventCount int64
}

// Distinct devices that have reported, for the admin device filter +
// "who is currently reporting" overview. Optionally scoped to one account.
func (q *Queries) ListDiagnosticDevices(ctx context.Context, userID pgtype.UUID) ([]ListDiagnosticDevicesRow, error) {
	rows, err := q.db.Query(ctx, listDiagnosticDevices, userID)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	var items []ListDiagnosticDevicesRow
	for rows.Next() {
		var i ListDiagnosticDevicesRow
		if err := rows.Scan(
			&i.ClientID,
			&i.UserID,
			&i.Username,
			&i.AppVersion,
			&i.OsVersion,
			&i.LastSeen,
			&i.EventCount,
		); err != nil {
			return nil, err
		}
		items = append(items, i)
	}
	if err := rows.Err(); err != nil {
		return nil, err
	}
	return items, nil
}
