/**
 * Tenant FK ownership guards — cross-tenant injection defense.
 *
 * Verify body-supplied foreign keys belong to the caller's tenant before persisting.
 * Routes return HTTP 400 with `{ error: 'Invalid <field> reference' }`; query helpers
 * throw `InvalidTenantReferenceError` for defense-in-depth.
 */
import { and, eq } from 'drizzle-orm'
import { createFkIsolation } from '@platform-modules/tenancy/isolation-fk'
import type { Db, DbTx } from '../client'
import { projects } from '../schema/projects'
import { customers, customerContacts } from '../schema/customers'
import { tasks, taskStatuses } from '../schema/tasks'
import { ticketCategories } from '../schema/support'
import { leads } from '../schema/marketing'
import { invoices } from '../schema/invoices'
import { kbSpaces, kbArticles } from '../schema/kb'
import { tenantMemberships } from '../schema/rbac'

type DbLike = Db | DbTx
const tenantOwnershipIsolation = createFkIsolation({
  tables: [
    customers,
    invoices,
    kbArticles,
    kbSpaces,
    leads,
    projects,
    tasks,
    taskStatuses,
    ticketCategories,
  ],
})

async function ownsScopedRow(
  db: DbLike,
  tenantId: string,
  table: unknown,
  idColumn: unknown,
  id: string | null | undefined,
): Promise<boolean> {
  if (id == null) return true
  const scoped = tenantOwnershipIsolation.scopeQuerier(db as never, tenantId) as any
  const rows = (await scoped
    .select({ id: idColumn })
    .from(table)
    .where(eq(idColumn as never, id))
    .limit(1)) as Array<{ id: string }>
  return Array.isArray(rows) && rows.length > 0
}

export class InvalidTenantReferenceError extends Error {
  readonly field: string

  constructor(field: string) {
    super(`Invalid ${field} reference`)
    this.name = 'InvalidTenantReferenceError'
    this.field = field
  }
}

/** JSON body fragment for route handlers. */
export function invalidTenantReferenceBody(field: string): { error: string } {
  return { error: `Invalid ${field} reference` }
}

export function assertTenantOwnsOrThrow(field: string, ok: boolean): void {
  if (!ok) throw new InvalidTenantReferenceError(field)
}

export async function assertTenantOwnsProject(
  db: DbLike,
  tenantId: string,
  projectId: string | null | undefined,
): Promise<boolean> {
  return ownsScopedRow(db, tenantId, projects, projects.id, projectId)
}

export async function assertTenantOwnsCustomer(
  db: DbLike,
  tenantId: string,
  customerId: string | null | undefined,
): Promise<boolean> {
  return ownsScopedRow(db, tenantId, customers, customers.id, customerId)
}

export async function assertTenantOwnsLead(
  db: DbLike,
  tenantId: string,
  leadId: string | null | undefined,
): Promise<boolean> {
  return ownsScopedRow(db, tenantId, leads, leads.id, leadId)
}

export async function assertTenantOwnsInvoice(
  db: DbLike,
  tenantId: string,
  invoiceId: string | null | undefined,
): Promise<boolean> {
  return ownsScopedRow(db, tenantId, invoices, invoices.id, invoiceId)
}

export async function assertTenantOwnsKbSpace(
  db: DbLike,
  tenantId: string,
  spaceId: string | null | undefined,
): Promise<boolean> {
  return ownsScopedRow(db, tenantId, kbSpaces, kbSpaces.id, spaceId)
}

export async function assertTenantOwnsKbArticle(
  db: DbLike,
  tenantId: string,
  articleId: string | null | undefined,
): Promise<boolean> {
  return ownsScopedRow(db, tenantId, kbArticles, kbArticles.id, articleId)
}

export async function assertTenantOwnsTask(
  db: DbLike,
  tenantId: string,
  taskId: string | null | undefined,
): Promise<boolean> {
  return ownsScopedRow(db, tenantId, tasks, tasks.id, taskId)
}

export async function assertTenantOwnsTaskStatus(
  db: DbLike,
  tenantId: string,
  statusId: string | null | undefined,
): Promise<boolean> {
  return ownsScopedRow(db, tenantId, taskStatuses, taskStatuses.id, statusId)
}

export async function assertTenantOwnsTicketCategory(
  db: DbLike,
  tenantId: string,
  categoryId: string | null | undefined,
): Promise<boolean> {
  return ownsScopedRow(db, tenantId, ticketCategories, ticketCategories.id, categoryId)
}

/** Assignee must be an active member of the caller's tenant. */
export async function assertActiveTenantAssignee(
  db: DbLike,
  tenantId: string,
  userId: string | null | undefined,
): Promise<boolean> {
  if (userId == null) return true
  const [row] = await db
    .select({ userId: tenantMemberships.userId })
    .from(tenantMemberships)
    .where(
      and(
        eq(tenantMemberships.userId, userId),
        eq(tenantMemberships.tenantId, tenantId),
        eq(tenantMemberships.status, 'active'),
      ),
    )
    .limit(1)
  return row != null
}

/**
 * Contact must belong to a customer owned by the tenant.
 * When customerId is supplied, the contact must belong to that customer.
 */
export async function assertTenantOwnsContact(
  db: DbLike,
  tenantId: string,
  contactId: string | null | undefined,
  customerId?: string | null,
): Promise<boolean> {
  if (contactId == null) return true
  const [row] = await db
    .select({
      id: customerContacts.id,
      customerId: customerContacts.customerId,
    })
    .from(customerContacts)
    .innerJoin(customers, eq(customers.id, customerContacts.customerId))
    .where(and(eq(customerContacts.id, contactId), eq(customers.tenantId, tenantId)))
    .limit(1)
  if (!row) return false
  if (customerId != null && row.customerId !== customerId) return false
  return true
}
