/**
 * Custom role permission superset guard — S2-004 regression tests.
 */
import { describe, it, expect, vi, beforeEach } from 'vitest'
import { Hono } from 'hono'
import type { AppEnv } from '../src/types'

const memberDelegatedPermissions = ['users:manage', 'customers:read']

const mockSession = {
  type: 'user' as const,
  sub: 'member-user-id',
  tid: 'tenant-id',
  email: 'member@example.com',
  name: 'Delegated Member',
  role: 'MEMBER',
  permissions: memberDelegatedPermissions,
  tier: 'business' as const,
  v: 1,
}

vi.mock('../src/middleware/auth', () => ({
  authMiddleware: async (c: { set: (key: string, value: unknown) => void }, next: () => Promise<void>) => {
    c.set('db', {})
    c.set('session', mockSession)
    await next()
  },
}))

vi.mock('@zync/db/queries', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@zync/db/queries')>()
  return {
    ...actual,
    createCustomRole: vi.fn(),
    updateCustomRole: vi.fn(),
  }
})

import { rolesRoute } from '../src/routes/roles'
import { createCustomRole, updateCustomRole } from '@zync/db/queries'

const mockEnv = {
  DB: { connectionString: 'postgresql://test:test@localhost/test' },
  JWT_SECRET: 'test-jwt-secret',
} as AppEnv['Bindings']

function appWithRolesRoute() {
  const app = new Hono<AppEnv>()
  app.route('/api/roles', rolesRoute)
  return app
}

describe('POST /api/roles permission superset guard (S2-004)', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('returns 403 when actor defines permissions beyond their own set', async () => {
    const res = await appWithRolesRoute().request('/api/roles', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Origin: 'https://app.zync.is',
      },
      body: JSON.stringify({
        name: 'Escalated Role',
        permissions: ['users:manage', 'customers:read', 'settings:write'],
      }),
    })

    expect(res.status).toBe(403)
    const body = (await res.json()) as { error: string }
    expect(body.error).toBe('Cannot grant a role exceeding your own permissions')
    expect(createCustomRole).not.toHaveBeenCalled()
  })

  it('allows creating a role whose permissions are a subset of the actor set', async () => {
    vi.mocked(createCustomRole).mockResolvedValue({
      id: 'role-id',
      name: 'Scoped Role',
      is_system_role: false,
      permissions: ['customers:read'],
      member_count: 0,
    })

    const res = await appWithRolesRoute().request('/api/roles', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        Origin: 'https://app.zync.is',
      },
      body: JSON.stringify({
        name: 'Scoped Role',
        permissions: ['customers:read'],
      }),
    })

    expect(res.status).toBe(201)
    expect(createCustomRole).toHaveBeenCalledOnce()
  })
})

describe('PATCH /api/roles/:id permission superset guard (S2-004)', () => {
  beforeEach(() => {
    vi.clearAllMocks()
  })

  it('returns 403 when actor expands role permissions beyond their own set', async () => {
    const res = await appWithRolesRoute().request('/api/roles/role-id', {
      method: 'PATCH',
      headers: {
        'Content-Type': 'application/json',
        Origin: 'https://app.zync.is',
      },
      body: JSON.stringify({
        permissions: ['users:manage', 'invoices:write'],
      }),
    })

    expect(res.status).toBe(403)
    const body = (await res.json()) as { error: string }
    expect(body.error).toBe('Cannot grant a role exceeding your own permissions')
    expect(updateCustomRole).not.toHaveBeenCalled()
  })
})
