/**
 * Search service core — search-completeness spec §6.
 *
 * Assembles SearchResultGroup[] from per-entity queries, applying:
 *   - Module gating (§6.2): entity queries are skipped if their module is disabled
 *   - Permission gating (§9): entity queries are skipped if the user lacks the permission
 *   - CONTRACTOR role restrictions (§6.3): only assigned tasks visible
 *   - KB article draft filtering (§6.3): VIEWER/CONTRACTOR see published only
 *   - Ranking: items ranked by ts_rank_cd within group; groups ranked by total desc
 *   - Tie-breaking: by ENTITY_PRIORITY index
 */
import type { Db } from '../client'
import type { EntityType, SearchResultGroup, SearchResultItem } from '@zync/types'
import { ENTITY_PRIORITY, ENTITY_LABELS } from '@zync/types'
import { getEnabledModuleIds } from '../queries/tenant-modules'
import { buildTsquery } from './build-tsquery'
import {
  encodeCursor,
  decodeCursor,
  searchTasks,
  searchProjects,
  searchCustomers,
  searchInvoices,
  searchReceipts,
  searchExpenses,
  searchVendors,
  searchLeads,
  searchProposals,
  searchContracts,
  searchContractors,
  searchKbArticles,
  searchSupportTickets,
  searchTeamMembers,
} from './entity-queries'

// ── Module gates per entity type (§1, §6.2) ───────────────────────────────────
//
// NOTE: 'proposals', 'contracts', 'contractors' are not present in the ModuleId
// enum (`@zync/modules`). These entities have no explicit module toggle — the spec
// lists them as distinct module gates but the module manifest does not include them.
// They are treated as always-on (gate = 'system') so the permission check in §9
// remains the real access control gate. `vendor` spec says gate='vendors' but
// vendors belong to the expenses workflow — using 'expenses' aligns with the
// permission `expenses:read` (§9) and the ModuleId enum.

export const ENTITY_MODULE_GATE: Record<EntityType, string> = {
  task: 'tasks',
  project: 'projects',
  customer: 'customers',
  invoice: 'invoices',
  receipt: 'invoices',
  expense: 'expenses',
  vendor: 'expenses',       // vendors are part of expenses workflow
  lead: 'marketing',
  proposal: 'system',       // no module toggle; permission-gated only
  contract: 'system',       // no module toggle; permission-gated only
  contractor: 'system',     // no module toggle; permission-gated only
  kb_article: 'kb',
  support_ticket: 'crm',
  team_member: 'system',
}

// ── Required permissions per entity type (§9) ─────────────────────────────────

export const ENTITY_PERMISSION: Record<EntityType, string> = {
  task: 'tasks:read',
  project: 'projects:read',
  customer: 'customers:read',
  invoice: 'invoices:read',
  receipt: 'invoices:read',
  expense: 'expenses:read',
  vendor: 'expenses:read',
  lead: 'marketing:read',
  proposal: 'marketing:read',
  contract: 'contracts:read',
  contractor: 'payouts:read',
  kb_article: 'kb:read',
  support_ticket: 'tickets:read',
  team_member: 'users:read',
}

// ── SearchParams interface ────────────────────────────────────────────────────

export interface SearchParams {
  db: Db
  tenantId: string
  userId: string
  /** JWT role e.g. 'OWNER' | 'CONTRACTOR' | 'ADMIN' | 'MEMBER' | 'VIEWER' */
  role: string
  /** JWT permissions[] */
  permissions: string[]
  /** Raw user query (already length-validated by the route) */
  query: string
  /** Per-group item cap */
  limit: number
  /** Null/undefined = all permitted+enabled types */
  types?: EntityType[] | null
  /** Only honored when exactly one type is specified */
  cursor?: string | null
}

export interface SearchEntitiesResult {
  groups: SearchResultGroup[]
  totalGroups: number
  nextCursor: string | null
  hasMore: boolean
}

// ── Main search function ──────────────────────────────────────────────────────

export async function searchEntities(p: SearchParams): Promise<SearchEntitiesResult> {
  const { db, tenantId, userId, role, permissions, query, limit, types, cursor } = p
  const isContractor = role === 'CONTRACTOR'

  const tsquery = buildTsquery(query)
  if (!tsquery) {
    return { groups: [], totalGroups: 0, nextCursor: null, hasMore: false }
  }

  // Build enabled module set (§6.2)
  const enabledModuleIds = await getEnabledModuleIds(db, tenantId)
  const enabledSet = new Set<string>(enabledModuleIds)
  enabledSet.add('system')

  const permSet = new Set<string>(permissions)

  // Determine which entity types to query
  const candidateTypes: EntityType[] = types && types.length > 0 ? types : ENTITY_PRIORITY

  // Filter candidates by module + permission + CONTRACTOR restrictions
  const permittedTypes = candidateTypes.filter((type) => {
    const moduleGate = ENTITY_MODULE_GATE[type]
    const permission = ENTITY_PERMISSION[type]

    if (!enabledSet.has(moduleGate)) return false
    if (!permSet.has(permission)) return false

    // CONTRACTOR can only search tasks (only assigned ones — enforced in query)
    if (isContractor && type !== 'task') return false

    return true
  })

  if (permittedTypes.length === 0) {
    return { groups: [], totalGroups: 0, nextCursor: null, hasMore: false }
  }

  // Cursor pagination — only supported when querying exactly one type
  const singleType = permittedTypes.length === 1 ? permittedTypes[0] : null
  let offset = 0
  if (singleType && cursor) {
    const decoded = decodeCursor(cursor)
    if (decoded && decoded.entityType === singleType) {
      offset = decoded.offset
    }
  }

  // Run all permitted entity queries concurrently
  const queryPromises = permittedTypes.map(async (type) => {
    const typeOffset = singleType === type ? offset : 0
    return { type, result: await runEntityQuery(db, type, tenantId, userId, role, isContractor, tsquery, limit, typeOffset) }
  })

  const results = await Promise.all(queryPromises)

  // Build groups, drop empty ones
  const groups: SearchResultGroup[] = []
  for (const { type, result } of results) {
    if (result.total > 0 || result.items.length > 0) {
      groups.push({
        type,
        label: ENTITY_LABELS[type],
        items: result.items as SearchResultItem[],
        total: result.total,
      })
    }
  }

  // Sort groups by total desc, ties broken by ENTITY_PRIORITY index
  groups.sort((a, b) => {
    if (b.total !== a.total) return b.total - a.total
    return ENTITY_PRIORITY.indexOf(a.type) - ENTITY_PRIORITY.indexOf(b.type)
  })

  // Cursor for next page (only when single type is queried)
  let nextCursor: string | null = null
  let hasMore = false
  if (singleType) {
    const group = groups.find((g) => g.type === singleType)
    if (group) {
      const nextOffset = offset + limit
      hasMore = nextOffset < group.total
      nextCursor = hasMore ? encodeCursor(singleType, nextOffset) : null
    }
  }

  return {
    groups,
    totalGroups: groups.length,
    nextCursor,
    hasMore,
  }
}

// ── Dispatcher — routes to the correct per-entity query ───────────────────────

async function runEntityQuery(
  db: Db,
  type: EntityType,
  tenantId: string,
  userId: string,
  role: string,
  isContractor: boolean,
  tsquery: string,
  limit: number,
  offset: number,
): Promise<{ items: unknown[]; total: number }> {
  switch (type) {
    case 'task':
      return searchTasks(db, tenantId, userId, isContractor, tsquery, limit, offset)
    case 'project':
      return searchProjects(db, tenantId, tsquery, limit, offset)
    case 'customer':
      return searchCustomers(db, tenantId, tsquery, limit, offset)
    case 'invoice':
      return searchInvoices(db, tenantId, tsquery, limit, offset)
    case 'receipt':
      return searchReceipts(db, tenantId, tsquery, limit, offset)
    case 'expense':
      return searchExpenses(db, tenantId, tsquery, limit, offset)
    case 'vendor':
      return searchVendors(db, tenantId, tsquery, limit, offset)
    case 'lead':
      return searchLeads(db, tenantId, tsquery, limit, offset)
    case 'proposal':
      return searchProposals(db, tenantId, tsquery, limit, offset)
    case 'contract':
      return searchContracts(db, tenantId, tsquery, limit, offset)
    case 'contractor':
      return searchContractors(db, tenantId, tsquery, limit, offset)
    case 'kb_article':
      return searchKbArticles(db, tenantId, role, tsquery, limit, offset)
    case 'support_ticket':
      return searchSupportTickets(db, tenantId, tsquery, limit, offset)
    case 'team_member':
      return searchTeamMembers(db, tenantId, tsquery, limit, offset)
  }
}
