import { describe, expect, it } from 'vitest'
import {
  ADMIN_WORKSPACES, CARRIER_COMPANY_ROLES, CARRIER_DISPATCH_ROLES, MODE_LABELS, ROLE_LABELS, ROLE_TYPES, accountContextKey, adminWorkspace,
  adminWorkspaceForContext, afterRemoval, contextDashboard, contextForRoleType, contextLabel, decideDeepLink, decideRemoval, decideSwitch, findContext,
  isAccountContextKey, isAdminPreview, isCarrierDispatchRole, pastContexts, pendingContexts, resolveLanding, roleTypeForAccountRole, roleTypeFromAccountKey,
  roleTypesForContext, switchableContexts, type RoleType, type UserContext,
} from '@/lib/domain/modes'

/**
 * One account, several roles, one active mode (Nash, 2026-09-24). Security and
 * navigation both hang off these rules, so they are pinned here.
 */
const ctx = (over: Partial<UserContext> & { key: string; roleType: UserContext['roleType'] }): UserContext => ({
  pageContext: contextForRoleType(over.roleType), status: 'active', companyId: 'c1', companyName: 'ABC Transport LLC',
  permission: 'member', independent: false, addedAt: '2026-09-01T00:00:00Z', removedAt: null, removedBy: null, ...over,
})

const dispatcher = ctx({ key: 'r1', roleType: 'carrier_dispatcher' })
const driver = ctx({ key: 'r2', roleType: 'carrier_driver' })
const broker = ctx({ key: 'r3', roleType: 'freight_broker', companyId: 'c2', companyName: 'Fast Freight Brokerage' })

describe('a role is always a role AT a company (§8)', () => {
  it('two dispatcher roles at two carriers are two different modes', () => {
    const abc = ctx({ key: 'r1', roleType: 'carrier_dispatcher' })
    const xyz = ctx({ key: 'r9', roleType: 'carrier_dispatcher', companyId: 'c9', companyName: 'XYZ Heavy Haul' })
    expect(contextLabel(abc)).toBe('Carrier Dispatcher — ABC Transport LLC')
    expect(contextLabel(xyz)).toBe('Carrier Dispatcher — XYZ Heavy Haul')
    expect(findContext([abc, xyz], 'r9')).toBe(xyz)
  })
  it('each role opens its own page group and they never merge (§11)', () => {
    expect(contextDashboard(dispatcher)).toBe('/cd-dashboard')
    expect(contextDashboard(driver)).toBe('/ct-dashboard')
    expect(contextDashboard(broker)).toBe('/fb-dashboard')
    expect(contextDashboard(ctx({ key: 'p', roleType: 'pilot_company_dispatch' }))).toBe('/pd-dashboard')
    expect(contextDashboard(ctx({ key: 'q', roleType: 'pilot_driver' }))).toBe('/pc-dashboard')
    // Carrier office roles (2026-09-30) share the Carrier Dispatch workspace.
    expect(roleTypesForContext('carrier')).toEqual(['carrier_dispatcher', 'carrier_safety_manager', 'carrier_permit_manager', 'carrier_accounting', 'carrier_fleet_manager'])
  })
  it('an account role with no company is an independent context', () => {
    expect(roleTypeForAccountRole('driver')).toBe('carrier_driver')
    expect(roleTypeForAccountRole('admin')).toBeNull()
    expect(isAccountContextKey(accountContextKey('pilot_driver'))).toBe(true)
    expect(roleTypeFromAccountKey('account:pilot_driver')).toBe('pilot_driver')
    expect(roleTypeFromAccountKey('account:nonsense')).toBeNull()
  })
})

describe('carrier office roles have Carrier Dispatcher powers (D15, 2026-09-30)', () => {
  const office: RoleType[] = ['carrier_safety_manager', 'carrier_permit_manager', 'carrier_accounting', 'carrier_fleet_manager']
  it('the four roles open the Carrier Dispatch workspace', () => {
    for (const r of office) {
      expect(contextForRoleType(r)).toBe('carrier')
      expect(contextDashboard(ctx({ key: r, roleType: r }))).toBe('/cd-dashboard')
      expect(MODE_LABELS[r]).toBe('Carrier Dispatch Mode')
    }
    expect(ROLE_LABELS.carrier_safety_manager).toBe('Safety Manager')
    expect(ROLE_LABELS.carrier_permit_manager).toBe('Permit Manager')
    expect(ROLE_LABELS.carrier_accounting).toBe('Accounting')
    expect(ROLE_LABELS.carrier_fleet_manager).toBe('Fleet Manager')
  })
  it('isCarrierDispatchRole is the power check — dispatcher and the four office roles, nobody else', () => {
    expect(isCarrierDispatchRole('carrier_dispatcher')).toBe(true)
    for (const r of office) expect(isCarrierDispatchRole(r)).toBe(true)
    expect(isCarrierDispatchRole('carrier_driver')).toBe(false)
    expect(isCarrierDispatchRole('freight_broker')).toBe(false)
    expect(isCarrierDispatchRole('pilot_company_dispatch')).toBe(false)
    expect(isCarrierDispatchRole('pilot_driver')).toBe(false)
    expect(isCarrierDispatchRole(null)).toBe(false)
    expect(isCarrierDispatchRole(undefined)).toBe(false)
    expect(isCarrierDispatchRole('company_admin')).toBe(false)
  })
  it('CARRIER_COMPANY_ROLES is what a carrier hands out: the five dispatcher-power roles plus Carrier Driver', () => {
    expect(CARRIER_DISPATCH_ROLES).toEqual(['carrier_dispatcher', ...office])
    expect(CARRIER_COMPANY_ROLES).toEqual([...CARRIER_DISPATCH_ROLES, 'carrier_driver'])
    expect(CARRIER_COMPANY_ROLES).not.toContain('freight_broker')
    expect(CARRIER_COMPANY_ROLES).not.toContain('pilot_company_dispatch')
    expect(CARRIER_COMPANY_ROLES).not.toContain('pilot_driver')
    for (const r of CARRIER_COMPANY_ROLES) expect(ROLE_TYPES).toContain(r)
  })
  it('a Safety Manager at a carrier reads like any other relationship and is a mode of its own', () => {
    const safety = ctx({ key: 's1', roleType: 'carrier_safety_manager' })
    expect(contextLabel(safety)).toBe('Safety Manager — ABC Transport LLC')
    expect(resolveLanding([safety], null)).toEqual({ kind: 'open', context: safety })
    const removal = decideRemoval({ contexts: [safety, dispatcher], key: 's1', companyAdminCount: 2 })
    expect(removal.ok).toBe(true)
    if (removal.ok) expect(removal.warning).toContain('Carrier Dispatch tools')
  })
})

describe('where a sign-in lands (§5, §6, §7)', () => {
  it('one active role opens straight away, no selector', () => {
    expect(resolveLanding([dispatcher], null)).toEqual({ kind: 'open', context: dispatcher })
  })
  it('several roles and no default asks', () => {
    const l = resolveLanding([dispatcher, driver, broker], null)
    expect(l.kind).toBe('choose')
    if (l.kind === 'choose') expect(l.contexts).toHaveLength(3)
  })
  it('a stored default opens straight away', () => {
    expect(resolveLanding([dispatcher, driver], 'r2')).toEqual({ kind: 'open', context: driver })
  })
  it('a default pointing at a role that is gone falls back to the selector', () => {
    expect(resolveLanding([dispatcher, driver], 'deleted').kind).toBe('choose')
  })
  it('only pending roles means nothing to open', () => {
    const l = resolveLanding([ctx({ key: 'p1', roleType: 'freight_broker', status: 'pending' })], null)
    expect(l.kind).toBe('none')
    if (l.kind === 'none') expect(l.pending).toHaveLength(1)
  })
  it('pending and past roles are never modes (§28, §29)', () => {
    const all = [dispatcher, ctx({ key: 'p', roleType: 'freight_broker', status: 'pending' }), ctx({ key: 'x', roleType: 'carrier_driver', status: 'revoked' })]
    expect(switchableContexts(all).map((c) => c.key)).toEqual(['r1'])
    expect(pendingContexts(all)).toHaveLength(1)
    expect(pastContexts(all)).toHaveLength(1)
  })
})

describe('switching is checked against what the person really holds (§26)', () => {
  const held = [dispatcher, driver, ctx({ key: 'p', roleType: 'freight_broker', status: 'pending', companyName: 'XYZ Brokerage' })]
  it('a held active role switches', () => {
    expect(decideSwitch(held, 'r2')).toEqual({ ok: true, context: driver })
  })
  it('a key the person does not hold is refused', () => {
    expect(decideSwitch(held, 'someone-elses-role')).toEqual({ ok: false, reason: 'That workspace is not on your account.' })
  })
  it('a pending role is refused with the reason (§28)', () => {
    const d = decideSwitch(held, 'p')
    expect(d.ok).toBe(false)
    if (!d.ok) expect(d.reason).toContain('waiting for verification')
  })
  it('a revoked role is refused (§29)', () => {
    const d = decideSwitch([ctx({ key: 'x', roleType: 'carrier_driver', status: 'revoked' })], 'x')
    expect(d.ok).toBe(false)
    if (!d.ok) expect(d.reason).toContain('revoked')
  })
})

describe('removing a role (§19–§22, §32)', () => {
  it('removing one role keeps the other at the same company', () => {
    const d = decideRemoval({ contexts: [dispatcher, driver], key: 'r2', companyAdminCount: 2 })
    expect(d.ok).toBe(true)
    if (d.ok) {
      expect(d.warning).toContain('ABC Transport LLC')
      expect(d.warning).toContain('You stay Carrier Dispatcher')
      expect(d.warning).toContain('your other roles stay active')
    }
  })
  it('the only Company Admin cannot orphan the company (§32, §50)', () => {
    const onlyAdmin = ctx({ key: 'r1', roleType: 'carrier_dispatcher', permission: 'company_admin' })
    const d = decideRemoval({ contexts: [onlyAdmin], key: 'r1', companyAdminCount: 1 })
    expect(d.ok).toBe(false)
    if (!d.ok) expect(d.reason).toBe('You are the only Company Admin for ABC Transport LLC. Assign another Company Admin before removing this role.')
  })
  it('a Company Admin with another role at the same company may drop this one (§33)', () => {
    const admin = ctx({ key: 'r1', roleType: 'carrier_driver', permission: 'company_admin' })
    const other = ctx({ key: 'r2', roleType: 'carrier_dispatcher', permission: 'company_admin' })
    expect(decideRemoval({ contexts: [admin, other], key: 'r1', companyAdminCount: 1 }).ok).toBe(true)
  })
  it('a role that comes from the account itself is not self-removable', () => {
    const independent = ctx({ key: 'account:carrier_driver', roleType: 'carrier_driver', independent: true, companyId: null, companyName: null })
    expect(decideRemoval({ contexts: [independent], key: 'account:carrier_driver', companyAdminCount: 0 }).ok).toBe(false)
  })
  it('an already inactive role cannot be removed twice', () => {
    expect(decideRemoval({ contexts: [ctx({ key: 'x', roleType: 'carrier_driver', status: 'revoked' })], key: 'x', companyAdminCount: 2 }).ok).toBe(false)
  })
})

describe('after a removal (§30, §31)', () => {
  it('removing the active role moves the person out of it', () => {
    const r = afterRemoval({ contexts: [dispatcher, driver], removedKey: 'r2', defaultKey: 'r1', activeKey: 'r2' })
    expect(r.nextActive).toBe(dispatcher)
    expect(r.redirect).toBe('/cd-dashboard')
    expect(r.clearDefault).toBe(false)
  })
  it('removing the default clears it and asks for a new one when several remain', () => {
    const r = afterRemoval({ contexts: [dispatcher, driver, broker], removedKey: 'r1', defaultKey: 'r1', activeKey: 'r1' })
    expect(r.clearDefault).toBe(true)
    expect(r.needsChoice).toBe(true)
    expect(r.redirect).toBe('/modes')
  })
  it('removing the last role returns the person to their profile', () => {
    const r = afterRemoval({ contexts: [driver], removedKey: 'r2', defaultKey: 'r2', activeKey: 'r2' })
    expect(r.nextActive).toBeNull()
    expect(r.redirect).toBe('/profile')
  })
  it('removing a role the person was not using leaves them where they are', () => {
    const r = afterRemoval({ contexts: [dispatcher, driver, broker], removedKey: 'r3', defaultKey: 'r1', activeKey: 'r1' })
    expect(r.redirect).toBe('/settings/roles')
    expect(r.needsChoice).toBe(false)
  })
})

describe('deep links (§34)', () => {
  const held = [dispatcher, broker]
  it('a link into the workspace already open just opens', () => {
    expect(decideDeepLink({ contexts: held, activeKey: 'r1', required: 'carrier', companyId: 'c1' })).toEqual({ kind: 'open' })
  })
  it('a link into another of the person\'s workspaces offers to switch', () => {
    const d = decideDeepLink({ contexts: held, activeKey: 'r3', required: 'carrier', companyId: 'c1', resourceLabel: 'This permit' })
    expect(d.kind).toBe('switch')
    if (d.kind === 'switch') {
      expect(d.context).toBe(dispatcher)
      expect(d.message).toBe('This permit belongs to your Carrier Dispatcher — ABC Transport LLC workspace.')
    }
  })
  it('holding one role never opens another company\'s resource', () => {
    const d = decideDeepLink({ contexts: held, activeKey: 'r1', required: 'carrier', companyId: 'other-company' })
    expect(d.kind).toBe('deny')
  })
  it('a page group the person holds no role for is denied', () => {
    expect(decideDeepLink({ contexts: held, activeKey: 'r1', required: 'pilot-driver' }).kind).toBe('deny')
  })
})

describe('Admin (§13–§17)', () => {
  it('offers every workspace, admin tools and product previews', () => {
    expect(ADMIN_WORKSPACES.filter((w) => w.group === 'admin').map((w) => w.label)).toEqual(['Admin Dashboard', 'Moderator Tools'])
    expect(ADMIN_WORKSPACES.filter((w) => w.group === 'product')).toHaveLength(5)
    expect(new Set(ADMIN_WORKSPACES.map((w) => w.key)).size).toBe(ADMIN_WORKSPACES.length)
  })
  it('a product view is a preview, admin tools are not', () => {
    expect(isAdminPreview(adminWorkspace('carrier'))).toBe(true)
    expect(isAdminPreview(adminWorkspace('admin_dashboard'))).toBe(false)
    expect(isAdminPreview(adminWorkspace('nope'))).toBe(false)
  })
  it('each page group has exactly one admin preview', () => {
    for (const c of ['broker', 'carrier', 'pilot-company', 'carrier-driver', 'pilot-driver'] as const) {
      expect(adminWorkspaceForContext(c)?.pageContext).toBe(c)
    }
  })
  it('there is no Super Admin anywhere in the model', () => {
    const text = JSON.stringify(ADMIN_WORKSPACES).toLowerCase()
    expect(text).not.toContain('super')
  })
})
