/** @platform-modules/tenancy core — contract seam over injected RBAC + isolation adapters. */

import type {
  Querier,
  Transaction,
  TransactionalDatabase,
} from '@platform-modules/db'
import type { InferInsertModel, SQL, Table } from 'drizzle-orm'

export type TenantStatus = 'active' | 'suspended' | 'pending'
export type MembershipStatus = 'active' | 'frozen' | 'pending_approval'

export type Tenant = {
  id: string
  slug: string
  name: string
  status: TenantStatus
  ownerId: string
  createdAt: Date
}

export type Membership = {
  tenantId: string
  userId: string
  roleKey: string
  status: MembershipStatus
}

export type CreateTenantInput = {
  slug: string
  name: string
  ownerId: string
  status?: TenantStatus
}

export type TenancyStore = {
  createTenant(input: CreateTenantInput): Promise<Tenant>
  getTenant(tenantId: string): Promise<Tenant | null>
  getTenantBySlug(slug: string): Promise<Tenant | null>
  suspendTenant(tenantId: string): Promise<Tenant>
  addMember(membership: Membership): Promise<Membership>
  removeMember(tenantId: string, userId: string): Promise<void>
  setMemberRole(tenantId: string, userId: string, roleKey: string): Promise<Membership>
  freezeMember(tenantId: string, userId: string): Promise<Membership>
  listMembers(tenantId: string): Promise<Membership[]>
  getMembership(tenantId: string, userId: string): Promise<Membership | null>
}

export type RunInTenantFn<S extends Record<string, unknown> = Record<string, never>> = <
  T,
>(
  db: TransactionalDatabase<S>,
  tenantId: string,
  fn: (scoped: Transaction<S>) => Promise<T>,
  opts?: { gucName?: string; role?: string },
) => Promise<T>

export type ScopedSelectBuilder = {
  from(table: Table, ...rest: unknown[]): unknown
}

type SnakeToCamel<S extends string> = S extends `${infer Head}_${infer Tail}`
  ? `${Head}${Capitalize<SnakeToCamel<Tail>>}`
  : S

export type ScopedTenantOmitKeys<T extends Table, C extends string> = Extract<
  keyof InferInsertModel<T>,
  C | SnakeToCamel<C>
>

export type ScopedInsertValues<T extends Table, C extends string> = Omit<
  InferInsertModel<T>,
  ScopedTenantOmitKeys<T, C>
>

export type ScopedInsertBuilder<TTable extends Table = Table, C extends string = never> = {
  values(
    values: ScopedInsertValues<TTable, C> | ScopedInsertValues<TTable, C>[],
  ): unknown
}

export type ScopedUpdateBuilder<TTable extends Table = Table, C extends string = never> = {
  set(patch: Partial<ScopedInsertValues<TTable, C>>): unknown
}

export type ScopedQuerier<
  S extends Record<string, unknown> = Record<string, never>,
  C extends string = never,
> = {
  select: (...args: unknown[]) => ScopedSelectBuilder
  selectDistinct: (...args: unknown[]) => ScopedSelectBuilder
  selectDistinctOn: (...args: unknown[]) => ScopedSelectBuilder
  insert: <TTable extends Table>(table: TTable) => ScopedInsertBuilder<TTable, C>
  update: <TTable extends Table>(table: TTable) => ScopedUpdateBuilder<TTable, C>
  delete: <TTable extends Table>(table: TTable) => { where: (condition?: SQL) => unknown }
}

export type IsolationAdapter<
  S extends Record<string, unknown> = Record<string, never>,
  C extends string = never,
> = {
  scopeQuerier(q: Querier<S>, tenantId: string): ScopedQuerier<S, C>
}

export type RbacResolveInput = {
  userId: string
  tenantId: string
  roleKey: string
}

export type RbacAdapter = {
  resolveCapabilities(input: RbacResolveInput): Promise<string[]>
}

export class TenancyError extends Error {}

export class TenantNotFoundError extends TenancyError {
  override readonly name = 'TenantNotFoundError'
  constructor(
    message: string,
    readonly tenantId: string,
  ) {
    super(message)
  }
}

export class NotAMemberError extends TenancyError {
  override readonly name = 'NotAMemberError'
  constructor(
    message: string,
    readonly tenantId: string,
    readonly userId: string,
  ) {
    super(message)
  }
}

export class MembershipFrozenError extends TenancyError {
  override readonly name = 'MembershipFrozenError'
  constructor(
    message: string,
    readonly tenantId: string,
    readonly userId: string,
  ) {
    super(message)
  }
}

export class ScopeViolationError extends TenancyError {
  override readonly name = 'ScopeViolationError'
  constructor(
    message: string,
    readonly tenantId: string,
  ) {
    super(message)
  }
}

export type TenancyDeps<S extends Record<string, unknown> = Record<string, never>> = {
  store: TenancyStore
  rbac: RbacAdapter
  isolation?: IsolationAdapter<S>
}

export type Tenancy<S extends Record<string, unknown> = Record<string, never>> = {
  scopeQuerier?(q: Querier<S>, tenantId: string): ScopedQuerier<S>
  resolveCapabilities(userId: string, tenantId: string): Promise<string[]>
  createTenant(input: CreateTenantInput): Promise<Tenant>
  getTenant(tenantId: string): Promise<Tenant | null>
  suspendTenant(tenantId: string): Promise<Tenant>
  addMember(membership: Membership): Promise<Membership>
  removeMember(tenantId: string, userId: string): Promise<void>
  setMemberRole(tenantId: string, userId: string, roleKey: string): Promise<Membership>
  freezeMember(tenantId: string, userId: string): Promise<Membership>
  listMembers(tenantId: string): Promise<Membership[]>
}

function randomId(): string {
  return crypto.randomUUID()
}

export function createMemoryTenancyStore(): TenancyStore {
  const tenants = new Map<string, Tenant>()
  const bySlug = new Map<string, string>()
  const memberships = new Map<string, Membership>()

  const memberKey = (tenantId: string, userId: string) => `${tenantId}:${userId}`

  return {
    async createTenant(input) {
      const tenant: Tenant = {
        id: randomId(),
        slug: input.slug,
        name: input.name,
        status: input.status ?? 'active',
        ownerId: input.ownerId,
        createdAt: new Date(),
      }
      tenants.set(tenant.id, tenant)
      bySlug.set(tenant.slug, tenant.id)
      return tenant
    },
    async getTenant(tenantId) {
      return tenants.get(tenantId) ?? null
    },
    async getTenantBySlug(slug) {
      const id = bySlug.get(slug)
      return id ? (tenants.get(id) ?? null) : null
    },
    async suspendTenant(tenantId) {
      const tenant = tenants.get(tenantId)
      if (!tenant) throw new TenantNotFoundError('tenant not found', tenantId)
      const updated = { ...tenant, status: 'suspended' as const }
      tenants.set(tenantId, updated)
      return updated
    },
    async addMember(membership) {
      memberships.set(memberKey(membership.tenantId, membership.userId), { ...membership })
      return membership
    },
    async removeMember(tenantId, userId) {
      memberships.delete(memberKey(tenantId, userId))
    },
    async setMemberRole(tenantId, userId, roleKey) {
      const key = memberKey(tenantId, userId)
      const existing = memberships.get(key)
      if (!existing) throw new NotAMemberError('not a member', tenantId, userId)
      const updated = { ...existing, roleKey }
      memberships.set(key, updated)
      return updated
    },
    async freezeMember(tenantId, userId) {
      const key = memberKey(tenantId, userId)
      const existing = memberships.get(key)
      if (!existing) throw new NotAMemberError('not a member', tenantId, userId)
      const updated = { ...existing, status: 'frozen' as const }
      memberships.set(key, updated)
      return updated
    },
    async listMembers(tenantId) {
      return [...memberships.values()].filter((m) => m.tenantId === tenantId)
    },
    async getMembership(tenantId, userId) {
      return memberships.get(memberKey(tenantId, userId)) ?? null
    },
  }
}

async function resolveCapabilitiesClosed(
  store: TenancyStore,
  rbac: RbacAdapter,
  userId: string,
  tenantId: string,
): Promise<string[]> {
  const tenant = await store.getTenant(tenantId)
  if (!tenant || tenant.status !== 'active') return []

  const membership = await store.getMembership(tenantId, userId)
  if (!membership || membership.status !== 'active') return []

  return rbac.resolveCapabilities({
    userId,
    tenantId,
    roleKey: membership.roleKey,
  })
}

export function createTenancy<S extends Record<string, unknown> = Record<string, never>>(
  deps: TenancyDeps<S>,
): Tenancy<S> {
  const { store, isolation, rbac } = deps

  return {
    ...(isolation
      ? { scopeQuerier: (q: Querier<S>, tenantId: string) => isolation.scopeQuerier(q, tenantId) }
      : {}),
    resolveCapabilities: (userId, tenantId) =>
      resolveCapabilitiesClosed(store, rbac, userId, tenantId),
    createTenant: (input) => store.createTenant(input),
    getTenant: (tenantId) => store.getTenant(tenantId),
    suspendTenant: async (tenantId) => {
      const tenant = await store.suspendTenant(tenantId)
      return tenant
    },
    addMember: (membership) => store.addMember(membership),
    removeMember: (tenantId, userId) => store.removeMember(tenantId, userId),
    setMemberRole: (tenantId, userId, roleKey) =>
      store.setMemberRole(tenantId, userId, roleKey),
    freezeMember: (tenantId, userId) => store.freezeMember(tenantId, userId),
    listMembers: (tenantId) => store.listMembers(tenantId),
  }
}
