/**
 * KB spaces management repository — settings-kb (wave-14).
 *
 * Provides space listing (with article counts), rename, reorder, and
 * delete-if-empty operations.  All helpers are tenant-scoped.
 */
import { eq, and, asc, count, max, sql } from 'drizzle-orm'
import type { Db } from '../client'
import { kbSpaces, kbArticles } from '../schema'

// ── Types ─────────────────────────────────────────────────────────────────────

export interface KbSpaceListItem {
  id: string
  tenantId: string
  name: string
  slug: string
  type: 'internal' | 'vault'
  customerId: string | null
  icon: string | null
  isPublic: boolean
  description: string | null
  position: number
  articleCount: number
  createdBy: string
  createdAt: string
}

// ── Typed errors ──────────────────────────────────────────────────────────────

export class KbSpaceHasArticlesError extends Error {
  articleCount: number
  constructor(articleCount: number) {
    super(`Space has ${articleCount} articles — move or delete them first`)
    this.name = 'KbSpaceHasArticlesError'
    this.articleCount = articleCount
  }
}

export class KbSpaceNotFoundError extends Error {
  constructor() {
    super('Space not found')
    this.name = 'KbSpaceNotFoundError'
  }
}

export class KbVaultSpaceDeleteError extends Error {
  constructor() {
    super('Vault spaces cannot be deleted from settings — manage from customer detail page')
    this.name = 'KbVaultSpaceDeleteError'
  }
}

// ── Queries ───────────────────────────────────────────────────────────────────

/**
 * List all spaces for a tenant, ordered by position then name,
 * with a live article count for each space.
 */
export async function listSpacesWithCounts(db: Db, tenantId: string): Promise<KbSpaceListItem[]> {
  const rows = await db
    .select({
      id: kbSpaces.id,
      tenantId: kbSpaces.tenantId,
      name: kbSpaces.name,
      slug: kbSpaces.slug,
      type: kbSpaces.type,
      customerId: kbSpaces.customerId,
      icon: kbSpaces.icon,
      isPublic: kbSpaces.isPublic,
      description: kbSpaces.description,
      position: kbSpaces.position,
      createdBy: kbSpaces.createdBy,
      createdAt: kbSpaces.createdAt,
      articleCount: count(kbArticles.id),
    })
    .from(kbSpaces)
    .leftJoin(kbArticles, and(eq(kbArticles.spaceId, kbSpaces.id), sql`${kbArticles.deletedAt} IS NULL`))
    .where(eq(kbSpaces.tenantId, tenantId))
    .groupBy(kbSpaces.id)
    .orderBy(asc(kbSpaces.position), asc(kbSpaces.name))

  return rows.map((r) => ({
    id: r.id,
    tenantId: r.tenantId,
    name: r.name,
    slug: r.slug,
    type: r.type as 'internal' | 'vault',
    customerId: r.customerId ?? null,
    icon: r.icon ?? null,
    isPublic: r.isPublic,
    description: r.description ?? null,
    position: r.position,
    articleCount: Number(r.articleCount),
    createdBy: r.createdBy,
    createdAt: r.createdAt instanceof Date ? r.createdAt.toISOString() : String(r.createdAt),
  }))
}

/**
 * Update a space's name and slug.
 * Throws `KbSpaceNotFoundError` if the space doesn't belong to the tenant.
 */
export async function renameSpace(
  db: Db,
  tenantId: string,
  spaceId: string,
  name: string,
  slug: string,
): Promise<KbSpaceListItem> {
  const [updated] = await db
    .update(kbSpaces)
    .set({ name, slug })
    .where(and(eq(kbSpaces.id, spaceId), eq(kbSpaces.tenantId, tenantId)))
    .returning()

  if (!updated) throw new KbSpaceNotFoundError()

  const [withCount] = await listSpacesWithCounts(db, tenantId).then((list) =>
    list.filter((s) => s.id === spaceId),
  )
  return withCount ?? { ...updated, type: updated.type as 'internal' | 'vault', customerId: updated.customerId ?? null, icon: updated.icon ?? null, description: updated.description ?? null, position: updated.position, articleCount: 0, createdAt: updated.createdAt instanceof Date ? updated.createdAt.toISOString() : String(updated.createdAt) }
}

/**
 * Reorder a space by updating its position value.
 * Throws `KbSpaceNotFoundError` if the space doesn't belong to the tenant.
 */
export async function reorderSpace(
  db: Db,
  tenantId: string,
  spaceId: string,
  position: number,
): Promise<void> {
  const result = await db
    .update(kbSpaces)
    .set({ position })
    .where(and(eq(kbSpaces.id, spaceId), eq(kbSpaces.tenantId, tenantId)))
    .returning({ id: kbSpaces.id })

  if (result.length === 0) throw new KbSpaceNotFoundError()
}

/**
 * Delete a space only if it has no articles.
 *
 * - Vault spaces cannot be deleted from settings (KbVaultSpaceDeleteError).
 * - Spaces with articles throw KbSpaceHasArticlesError with the count.
 * - If the deleted space is the tenant's default, that FK nulls out automatically (ON DELETE SET NULL).
 */
export async function deleteSpaceIfEmpty(db: Db, tenantId: string, spaceId: string): Promise<void> {
  const [space] = await db
    .select({ id: kbSpaces.id, type: kbSpaces.type })
    .from(kbSpaces)
    .where(and(eq(kbSpaces.id, spaceId), eq(kbSpaces.tenantId, tenantId)))
    .limit(1)

  if (!space) throw new KbSpaceNotFoundError()
  if (space.type === 'vault') throw new KbVaultSpaceDeleteError()

  const countResult = await db
    .select({ articleCount: count(kbArticles.id) })
    .from(kbArticles)
    .where(and(eq(kbArticles.spaceId, spaceId), sql`${kbArticles.deletedAt} IS NULL`))

  const n = Number(countResult[0]?.articleCount ?? 0)
  if (n > 0) throw new KbSpaceHasArticlesError(n)

  await db.delete(kbSpaces).where(and(eq(kbSpaces.id, spaceId), eq(kbSpaces.tenantId, tenantId)))
}

/**
 * Return the next available position (MAX(position) + 1) for a new space
 * in a given tenant.
 */
export async function nextSpacePosition(db: Db, tenantId: string): Promise<number> {
  const posResult = await db
    .select({ maxPos: max(kbSpaces.position) })
    .from(kbSpaces)
    .where(eq(kbSpaces.tenantId, tenantId))

  return (posResult[0]?.maxPos ?? -1) + 1
}
