import { pgTable, serial, integer, text, timestamp, uniqueIndex } from 'drizzle-orm/pg-core';
import { users } from '../auth/schema';

// Where a self-hosted service lives, and what it takes to talk to it — for services the owner has exactly
// ONE of. Transmission and slskd today.
//
// This used to be TRANSMISSION_URL / SLSKD_URL / SLSKD_API_KEY in the platform-wide `.env`, which was wrong
// twice over: Bun auto-loads `.env` into EVERY process started in the platform directory, so `officer`
// itself held a slskd credential it has no code to use — and pointing Officer at a daemon was a shell task
// on the server rather than something the owner could do from the app.
//
// NOT a registry, unlike photos_config and invoiceshelf_accounts. Those are plural because the same person
// really does have two Immich users or two companies' books. A second Soulseek daemon or a second
// Transmission is not a thing anyone has, so there is no label, no `is_active`, no switcher — one row per
// (owner, service), enforced below. If that ever stops being true the shape here grows into theirs; until
// then the simpler thing is the honest one.
//
// `secret` is encrypted at rest via ../crypto.ts (a slskd API key drives the whole daemon; Transmission's
// RPC password is the owner's). It is nullable because Transmission is normally run with no RPC auth at
// all, which is not the same as an empty password — see getAuthHeader in the transmission sidecar.
export const serviceConnections = pgTable(
  'service_connections',
  {
    id: serial('id').primaryKey(),
    userId: integer('user_id')
      .notNull()
      .references(() => users.id, { onDelete: 'cascade' }),
    /** 'transmission' | 'slskd'. Text, not an enum: adding a service should not be a schema change. */
    service: text('service').notNull(),
    // Normalized without a trailing slash before write, so `${url}/api/...` never doubles the separator.
    //
    // NULL means "inherit the instance" — the row is a CREDENTIAL, not a connection.
    //
    // Some services are one instance that several people authenticate to individually: Gitea is the first.
    // The owner's row carries the URL and is the instance; every other user's row carries only their own
    // access token and resolves the base from the owner's row at read time. A member's URL is therefore not
    // stored rather than merely hidden, which is what makes "members never see the instance URL" a property
    // of the schema instead of a filter somebody has to remember on every response.
    //
    // It also closes the obvious hole: if a member could supply a URL, the sidecar would dutifully talk to
    // whatever host they named, which is a per-user SSRF hop wearing a settings form.
    //
    // Single-daemon services (transmission, slskd, nbxplorer) always set it. Only the inheriting kind leaves
    // it null, and only for non-owner rows.
    url: text('url'),
    /** Transmission RPC basic-auth user. Null/empty means the daemon has no auth, which is the usual case. */
    username: text('username'),
    secret: text('secret'), // encrypted: slskd API key, or Transmission's RPC password
    /**
     * Service-specific endpoint path. Only Transmission uses it (`/transmission/rpc` by default) and only a
     * reverse proxy makes it differ. Null everywhere else rather than a second table for one column.
     */
    path: text('path'),
    /** Version seen at the last successful probe — shown in the UI, never used for behaviour. */
    version: text('version'),
    lastSeenAt: timestamp('last_seen_at', { withTimezone: true }),
    createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
    updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(),
  },
  // One connection per service per owner. This is what makes the whole "no active flag" simplification safe:
  // there is never a second row to choose between, so nothing can be ambiguous about which one is in use.
  (t) => [uniqueIndex('uq_service_connections_user_service').on(t.userId, t.service)],
);
