import 'server-only'

import { createAdminClient } from '@/lib/supabase/admin'
import { emailPattern } from '@/lib/like'
import {
  ACCOUNT_KEY_PREFIX, accountContextKey, decideRemoval, decideSwitch, isAccountContextKey, roleTypeForAccountRole,
  contextForRoleType, isCarrierDispatchRole, type RoleType, type UserContext,
} from '@/lib/domain/modes'

/**
 * Modes — the I/O (Nash, 2026-09-24). Rules in src/lib/domain/modes.ts.
 *
 * Needs migration 0034. Without it every account keeps exactly the behaviour
 * it had before: one context derived from the account role, no selector, no
 * Switch Mode.
 */

type Admin = ReturnType<typeof createAdminClient>
const missing = (e: { message?: string; code?: string } | null | undefined) =>
  !!e && (e.code === '42P01' || e.code === 'PGRST205' || /does not exist|schema cache|could not find the table/i.test(e.message ?? ''))

export interface Actor { id: string | null; label: string }

export async function modesAvailable(admin: Admin = createAdminClient()): Promise<boolean> {
  const { error } = await admin.from('membership_roles').select('id').limit(1)
  return !missing(error)
}

/* ---------------------------------------------------------- the contexts */

interface MembershipRoleRow {
  id: string
  membership_id: string
  company_id: string
  role_type: RoleType
  status: UserContext['status']
  added_at: string | null
  removed_at: string | null
  removed_by: string | null
}

/**
 * Every role this person holds, each one a role AT a company, plus the role
 * their account itself carries when no company relationship covers it — a
 * driver with no company is still a driver (§47 "Pilot Driver — Independent").
 */
export async function loadUserContexts(
  user: { id: string; role: string },
  admin: Admin = createAdminClient(),
): Promise<UserContext[]> {
  const out: UserContext[] = []
  const { data, error } = await admin
    .from('membership_roles')
    .select('id, membership_id, company_id, role_type, status, added_at, removed_at, removed_by')
    .eq('user_id', user.id)
    .order('added_at', { ascending: true })

  if (!error && data?.length) {
    const rows = data as MembershipRoleRow[]
    const companyIds = [...new Set(rows.map((r) => r.company_id))]
    const membershipIds = [...new Set(rows.map((r) => r.membership_id))]
    const [{ data: companies }, { data: memberships }] = await Promise.all([
      admin.from('companies').select('id, display_name, legal_name').in('id', companyIds),
      admin.from('company_memberships').select('id, status, permission_level').in('id', membershipIds),
    ])
    const nameById = new Map(((companies ?? []) as Array<{ id: string; display_name: string; legal_name: string }>).map((c) => [c.id, c.display_name || c.legal_name]))
    const memById = new Map(((memberships ?? []) as Array<{ id: string; status: string; permission_level?: string }>).map((m) => [m.id, m]))
    for (const r of rows) {
      const m = memById.get(r.membership_id)
      // A role can only be active while its company relationship is approved (§26).
      const status: UserContext['status'] =
        r.status === 'active' && m?.status !== 'approved' ? (m?.status === 'pending' ? 'pending' : 'revoked') : r.status
      out.push({
        key: r.id,
        roleType: r.role_type,
        pageContext: contextForRoleType(r.role_type),
        status,
        companyId: r.company_id,
        companyName: nameById.get(r.company_id) ?? null,
        permission: (m?.permission_level === 'company_admin' ? 'company_admin' : 'member'),
        independent: false,
        addedAt: r.added_at,
        removedAt: r.removed_at,
        removedBy: r.removed_by,
      })
    }
  }

  // The account's own role, when no company role already provides it. This is
  // what keeps every existing single-role login working untouched.
  // A carrier office role (Safety Manager, Fleet Manager…) carries Carrier Dispatcher
  // powers (2026-09-30), so it already covers a `dispatcher` account role.
  const accountRole = roleTypeForAccountRole(user.role)
  const covers = (c: UserContext) => c.status === 'active' && (c.roleType === accountRole || (accountRole === 'carrier_dispatcher' && isCarrierDispatchRole(c.roleType)))
  if (accountRole && !out.some(covers)) {
    out.push({
      key: accountContextKey(accountRole),
      roleType: accountRole,
      pageContext: contextForRoleType(accountRole),
      status: 'active',
      companyId: null,
      companyName: null,
      permission: 'member',
      independent: true,
      addedAt: null,
      removedAt: null,
      removedBy: null,
    })
  }
  return out
}

/* -------------------------------------------------------- default / last */

export interface ModePreferences { defaultKey: string | null; lastActiveKey: string | null; adminWorkspace: string | null }

export async function loadModePreferences(userId: string, admin: Admin = createAdminClient()): Promise<ModePreferences> {
  const { data, error } = await admin.from('auth_accounts').select('default_context_key, last_active_context_key, admin_default_workspace').eq('user_id', userId).maybeSingle()
  if (error || !data) return { defaultKey: null, lastActiveKey: null, adminWorkspace: null }
  const d = data as { default_context_key: string | null; last_active_context_key: string | null; admin_default_workspace: string | null }
  return { defaultKey: d.default_context_key, lastActiveKey: d.last_active_context_key, adminWorkspace: d.admin_default_workspace }
}

/** auth_accounts holds one row per login; an env login may not have one yet. */
async function upsertAccountPrefs(user: { id: string; email: string; name: string; role: string }, patch: Record<string, unknown>, admin: Admin) {
  const now = new Date().toISOString()
  const { error } = await admin.from('auth_accounts').upsert(
    { user_id: user.id, username: user.email, email: user.email, name: user.name, ...patch, updated_at: now },
    { onConflict: 'user_id' },
  )
  if (error && !missing(error)) {
    // The row may exist with a different username; update in place instead.
    const { error: e2 } = await admin.from('auth_accounts').update({ ...patch, updated_at: now }).eq('user_id', user.id)
    if (e2) return { ok: false as const, error: e2.message }
  }
  return { ok: true as const }
}

export async function setDefaultContext(
  user: { id: string; email: string; name: string; role: string },
  key: string | null,
  actor: Actor,
  admin: Admin = createAdminClient(),
) {
  if (key) {
    const contexts = await loadUserContexts(user, admin)
    const d = decideSwitch(contexts, key)
    if (!d.ok) return { ok: false as const, error: d.reason }
  }
  const r = await upsertAccountPrefs(user, { default_context_key: key }, admin)
  if (!r.ok) return r
  await logRoleAction({ userId: user.id, action: 'default_mode_changed', actor, detail: { default_context_key: key } }, admin)
  return { ok: true as const }
}

export async function setAdminDefaultWorkspace(user: { id: string; email: string; name: string; role: string }, workspace: string | null, actor: Actor, admin: Admin = createAdminClient()) {
  const r = await upsertAccountPrefs(user, { admin_default_workspace: workspace }, admin)
  if (!r.ok) return r
  await logRoleAction({ userId: user.id, action: 'default_mode_changed', actor, detail: { admin_default_workspace: workspace } }, admin)
  return { ok: true as const }
}

export async function rememberActiveContext(user: { id: string; email: string; name: string; role: string }, key: string, admin: Admin = createAdminClient()) {
  await upsertAccountPrefs(user, { last_active_context_key: key }, admin)
}

/* ------------------------------------------------------------ switching */

export async function switchContext(
  user: { id: string; email: string; name: string; role: string },
  key: string,
  actor: Actor,
  admin: Admin = createAdminClient(),
): Promise<{ ok: true; context: UserContext } | { ok: false; error: string }> {
  const contexts = await loadUserContexts(user, admin)
  const d = decideSwitch(contexts, key)
  if (!d.ok) return { ok: false, error: d.reason }
  await rememberActiveContext(user, key, admin)
  await logRoleAction({
    userId: user.id, membershipRoleId: isAccountContextKey(key) ? null : key, companyId: d.context.companyId,
    roleType: d.context.roleType, action: 'mode_switched', actor, detail: { to: key },
  }, admin)
  return { ok: true, context: d.context }
}

/* ------------------------------------------------------------- removal */

/** How many people hold Company Admin at a company right now (§32). */
export async function companyAdminCount(companyId: string, admin: Admin = createAdminClient()): Promise<number> {
  const { count } = await admin.from('company_memberships').select('id', { count: 'exact', head: true })
    .eq('company_id', companyId).eq('status', 'approved').eq('permission_level', 'company_admin')
  return count ?? 0
}

export async function removeRole(
  user: { id: string; email: string; name: string; role: string },
  key: string,
  actor: Actor,
  admin: Admin = createAdminClient(),
): Promise<{ ok: true; removed: UserContext } | { ok: false; error: string }> {
  const contexts = await loadUserContexts(user, admin)
  const target = contexts.find((c) => c.key === key)
  if (!target) return { ok: false, error: 'That workspace is not on your account.' }
  const adminCount = target.companyId ? await companyAdminCount(target.companyId, admin) : 0
  const d = decideRemoval({ contexts, key, companyAdminCount: adminCount })
  if (!d.ok) return { ok: false, error: d.reason }

  const now = new Date().toISOString()
  // §21: nothing is deleted — the role is marked inactive and history stays put.
  const { error } = await admin.from('membership_roles').update({
    status: 'removed_by_user', removed_at: now, removed_by: actor.label, removed_by_user_id: actor.id, updated_at: now,
  }).eq('id', key).eq('user_id', user.id)
  if (error) return { ok: false, error: missing(error) ? 'Roles need database migration 0034.' : error.message }

  const prefs = await loadModePreferences(user.id, admin)
  if (prefs.defaultKey === key) await upsertAccountPrefs(user, { default_context_key: null }, admin)
  if (prefs.lastActiveKey === key) await upsertAccountPrefs(user, { last_active_context_key: null }, admin)
  await logRoleAction({
    userId: user.id, membershipRoleId: key, companyId: target.companyId, roleType: target.roleType,
    action: 'role_removed_by_user', previousStatus: target.status, newStatus: 'removed_by_user', actor,
    detail: { company: target.companyName },
  }, admin)
  return { ok: true, removed: target }
}

/* ---------------------------------------------------------------- audit */

export async function logRoleAction(params: {
  userId: string
  membershipRoleId?: string | null
  companyId?: string | null
  roleType?: string | null
  action: string
  previousStatus?: string | null
  newStatus?: string | null
  actor: Actor
  detail?: Record<string, unknown> | null
}, admin: Admin = createAdminClient()): Promise<void> {
  const { error } = await admin.from('role_audit_log').insert({
    user_id: params.userId, membership_role_id: params.membershipRoleId ?? null, company_id: params.companyId ?? null,
    role_type: params.roleType ?? null, action: params.action, previous_status: params.previousStatus ?? null,
    new_status: params.newStatus ?? null, actor_label: params.actor.label, actor_user_id: params.actor.id, detail: params.detail ?? null,
  })
  if (error && !missing(error)) console.error('role_audit_log insert failed', error.message)
}

export async function loadRoleAudit(userId: string, limit = 30, admin: Admin = createAdminClient()) {
  const { data, error } = await admin.from('role_audit_log').select('*').eq('user_id', userId).order('created_at', { ascending: false }).limit(limit)
  if (error) return []
  return data ?? []
}

/* ------------------------------------------------- adding roles (company) */

/**
 * Give a membership another role — used when a company adds someone as both
 * dispatcher and driver (§23). Idempotent; a previously removed role comes
 * back as active.
 */
export async function addMembershipRole(params: {
  membershipId: string
  userId: string
  companyId: string
  roleType: RoleType
  status?: 'active' | 'pending'
  actor: Actor
}, admin: Admin = createAdminClient()) {
  const now = new Date().toISOString()
  const { data, error } = await admin.from('membership_roles').upsert({
    membership_id: params.membershipId, user_id: params.userId, company_id: params.companyId, role_type: params.roleType,
    status: params.status ?? 'active', added_at: now, added_by: params.actor.label, removed_at: null, removed_by: null, removed_by_user_id: null, updated_at: now,
  }, { onConflict: 'membership_id,role_type' }).select('id').single()
  if (error) return { ok: false as const, error: missing(error) ? 'Roles need database migration 0034.' : error.message }
  await logRoleAction({
    userId: params.userId, membershipRoleId: (data as { id: string }).id, companyId: params.companyId,
    roleType: params.roleType, action: 'role_added', newStatus: params.status ?? 'active', actor: params.actor,
  }, admin)
  return { ok: true as const, id: (data as { id: string }).id }
}

/** Mirror a membership becoming approved / revoked onto its roles. */
export async function syncMembershipRoleStatus(membershipId: string, status: 'active' | 'revoked', actor: Actor, admin: Admin = createAdminClient()) {
  const now = new Date().toISOString()
  const patch = status === 'revoked'
    ? { status: 'revoked', removed_at: now, removed_by: actor.label, updated_at: now }
    : { status: 'active', removed_at: null, removed_by: null, updated_at: now }
  const { error } = await admin.from('membership_roles').update(patch).eq('membership_id', membershipId).neq('status', 'removed_by_user')
  if (error && !missing(error)) console.error('membership role status sync failed', error.message)
}

/** The user id behind an email, for linking a role to an account that already exists. */
export async function userIdForEmail(email: string, admin: Admin = createAdminClient()): Promise<string | null> {
  const { data } = await admin.from('profiles').select('id').ilike('email', emailPattern(email.trim().toLowerCase())).maybeSingle()
  return (data as { id: string } | null)?.id ?? null
}

export { ACCOUNT_KEY_PREFIX }
