import { eq, and, desc } from 'drizzle-orm';
import { db } from '../db';
import { jellyfinServers } from './schema';
import { encryptSecret, decryptSecret } from '../crypto';

// Jellyfin server registry for the officer-jellyfin sidecar. Callers deal in PLAINTEXT — encryption to and
// from at-rest ciphertext happens here. See ../crypto.ts and ../schema/jellyfin.ts.
//
// Two return types, and the split is the safety property:
//   JellyfinServer      — safe to serialize to the browser. Has NO token field at all, not even masked.
//   JellyfinCredentials — url + decrypted token + the pinned Jellyfin user, for the sidecar's own calls.
// `serverCols` is what enforces it: a bare `select()` would put the ciphertext column into every list
// response the moment someone forgot to strip it.

export type JellyfinServer = {
  id: number;
  label: string;
  url: string;
  jellyfinUserId: string;
  jellyfinUsername: string | null;
  serverName: string | null;
  version: string | null;
  isActive: boolean;
  lastSeenAt: Date | null;
  createdAt: Date;
};

export type JellyfinCredentials = {
  id: number;
  label: string;
  url: string;
  accessToken: string;
  jellyfinUserId: string;
  deviceId: string;
};

const serverCols = {
  id: jellyfinServers.id,
  label: jellyfinServers.label,
  url: jellyfinServers.url,
  jellyfinUserId: jellyfinServers.jellyfinUserId,
  jellyfinUsername: jellyfinServers.jellyfinUsername,
  serverName: jellyfinServers.serverName,
  version: jellyfinServers.version,
  isActive: jellyfinServers.isActive,
  lastSeenAt: jellyfinServers.lastSeenAt,
  createdAt: jellyfinServers.createdAt,
};

const toCredentials = (row: typeof jellyfinServers.$inferSelect): JellyfinCredentials => ({
  id: row.id,
  label: row.label,
  url: row.url,
  accessToken: decryptSecret('jellyfin', row.accessToken),
  jellyfinUserId: row.jellyfinUserId,
  deviceId: row.deviceId,
});

/** Every server the owner has added, active first then newest. Never includes the token. */
export async function listJellyfinServers(userId: number): Promise<JellyfinServer[]> {
  return db
    .select(serverCols)
    .from(jellyfinServers)
    .where(eq(jellyfinServers.userId, userId))
    .orderBy(desc(jellyfinServers.isActive), desc(jellyfinServers.createdAt));
}

/** The selected server with its token decrypted, or null when none is added. */
export async function getActiveJellyfinCredentials(userId: number): Promise<JellyfinCredentials | null> {
  const [row] = await db
    .select()
    .from(jellyfinServers)
    .where(and(eq(jellyfinServers.userId, userId), eq(jellyfinServers.isActive, true)));
  return row ? toCredentials(row) : null;
}

/** One server's credentials by id — for probing a specific server rather than the active one. */
export async function getJellyfinCredentials(userId: number, id: number): Promise<JellyfinCredentials | null> {
  const [row] = await db
    .select()
    .from(jellyfinServers)
    .where(and(eq(jellyfinServers.userId, userId), eq(jellyfinServers.id, id)));
  return row ? toCredentials(row) : null;
}

type CreateJellyfinServerParams = {
  userId: number;
  label: string;
  url: string;
  accessToken: string;
  jellyfinUserId: string;
  jellyfinUsername: string | null;
  deviceId: string;
  serverName: string | null;
  version: string | null;
  /** Select it. True for the first server, so the UI is never left with servers added but none chosen. */
  activate: boolean;
};

/** Add a server. The token is encrypted before write; the returned row carries no token. */
export async function createJellyfinServer(params: CreateJellyfinServerParams): Promise<JellyfinServer> {
  const { userId, activate, accessToken, ...rest } = params;
  return db.transaction(async (tx) => {
    if (activate) {
      await tx
        .update(jellyfinServers)
        .set({ isActive: false, updatedAt: new Date() })
        .where(and(eq(jellyfinServers.userId, userId), eq(jellyfinServers.isActive, true)));
    }
    const [row] = await tx
      .insert(jellyfinServers)
      .values({
        userId,
        ...rest,
        accessToken: encryptSecret('jellyfin', accessToken),
        isActive: activate,
        lastSeenAt: rest.version ? new Date() : null,
      })
      .returning(serverCols);
    return row!;
  });
}

type UpdateJellyfinServerParams = {
  label?: string;
  url?: string;
  accessToken?: string;
  jellyfinUserId?: string;
  jellyfinUsername?: string | null;
  serverName?: string | null;
  version?: string | null;
};

/** Edit a server in place. Omitting `accessToken` keeps the stored one — re-authenticating replaces it. */
export async function updateJellyfinServer(
  userId: number,
  id: number,
  params: UpdateJellyfinServerParams,
): Promise<JellyfinServer | null> {
  const { accessToken, ...rest } = params;
  const [row] = await db
    .update(jellyfinServers)
    .set({
      ...rest,
      ...(accessToken ? { accessToken: encryptSecret('jellyfin', accessToken) } : {}),
      updatedAt: new Date(),
    })
    .where(and(eq(jellyfinServers.userId, userId), eq(jellyfinServers.id, id)))
    .returning(serverCols);
  return row ?? null;
}

/** Switch the selected server. Clearing the others and setting this one is one transaction, never two. */
export async function setActiveJellyfinServer(userId: number, id: number): Promise<JellyfinServer | null> {
  return db.transaction(async (tx) => {
    await tx
      .update(jellyfinServers)
      .set({ isActive: false, updatedAt: new Date() })
      .where(and(eq(jellyfinServers.userId, userId), eq(jellyfinServers.isActive, true)));
    const [row] = await tx
      .update(jellyfinServers)
      .set({ isActive: true, updatedAt: new Date() })
      .where(and(eq(jellyfinServers.userId, userId), eq(jellyfinServers.id, id)))
      .returning(serverCols);
    return row ?? null;
  });
}

/**
 * Remove a server. If it was the selected one, the newest remaining server takes its place — leaving the
 * owner with servers registered but none active would show the setup form over a working registry.
 */
export async function deleteJellyfinServer(userId: number, id: number): Promise<boolean> {
  return db.transaction(async (tx) => {
    const [gone] = await tx
      .delete(jellyfinServers)
      .where(and(eq(jellyfinServers.userId, userId), eq(jellyfinServers.id, id)))
      .returning({ id: jellyfinServers.id, wasActive: jellyfinServers.isActive });
    if (!gone) return false;
    if (gone.wasActive) {
      const [next] = await tx
        .select({ id: jellyfinServers.id })
        .from(jellyfinServers)
        .where(eq(jellyfinServers.userId, userId))
        .orderBy(desc(jellyfinServers.createdAt))
        .limit(1);
      if (next) {
        await tx
          .update(jellyfinServers)
          .set({ isActive: true, updatedAt: new Date() })
          .where(eq(jellyfinServers.id, next.id));
      }
    }
    return true;
  });
}

/** Stamp a successful probe: what version answered, and when. Failures deliberately leave the row alone. */
export async function recordJellyfinProbe(userId: number, id: number, version: string | null): Promise<void> {
  await db
    .update(jellyfinServers)
    .set({ version, lastSeenAt: new Date(), updatedAt: new Date() })
    .where(and(eq(jellyfinServers.userId, userId), eq(jellyfinServers.id, id)));
}
