import { beforeEach, describe, expect, it, vi } from 'vitest'
import { Hono } from 'hono'
import { TenantTier } from '@zync/types'
import type { AppEnv } from '../src/types'

const { requirePermissionMock, requireTierMock, registeredPermissions, registeredTiers } = vi.hoisted(() => {
  const registeredPermissions: string[] = []
  const registeredTiers: string[] = []
  return {
    requirePermissionMock: vi.fn((arg: unknown) => {
      registeredPermissions.push(arg as string)
      return async (_c: unknown, next: () => Promise<void>) => next()
    }),
    requireTierMock: vi.fn((arg: unknown) => {
      registeredTiers.push(arg as string)
      return async (_c: unknown, next: () => Promise<void>) => next()
    }),
    registeredPermissions,
    registeredTiers,
  }
})

const mockGetTenantFieldRules = vi.fn()
const mockSaveTenantFieldRules = vi.fn()

vi.mock('../src/middleware/auth', () => ({
  authMiddleware: async (_c: unknown, next: () => Promise<void>) => next(),
}))

vi.mock('../src/middleware/guards', () => ({
  requirePermission: (...args: unknown[]) => requirePermissionMock(...args),
  requireTier: (...args: unknown[]) => requireTierMock(...args),
}))

vi.mock('@zync/db/queries', async () => {
  const actual = await vi.importActual<Record<string, unknown>>('@zync/db/queries')
  return {
    ...actual,
    getTenantFieldRules: (...args: unknown[]) => mockGetTenantFieldRules(...args),
    saveTenantFieldRules: (...args: unknown[]) => mockSaveTenantFieldRules(...args),
  }
})

import { fieldPermissionsSettingsRoute } from '../src/routes/field-permissions/settings'
import { fieldPermissionsEffectiveRoute } from '../src/routes/field-permissions/effective'

function appFor(
  routeBase: string,
  route: Hono<AppEnv>,
  session: Record<string, unknown> = {
    type: 'user',
    sub: 'user-1',
    tid: 'tenant-1',
    role: 'CONTRACTOR',
    permissions: ['permissions:write'],
  },
) {
  const app = new Hono<AppEnv>()
  app.use('*', async (c, next) => {
    c.set('session', session)
    c.set('db', {})
    await next()
  })
  app.route(routeBase, route)
  return app
}

describe('field permissions settings route', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockGetTenantFieldRules.mockResolvedValue([
      {
        entityType: 'invoice',
        fieldName: 'total',
        role: 'CONTRACTOR',
        permission: 'hidden',
      },
    ])
    mockSaveTenantFieldRules.mockResolvedValue(undefined)
  })

  it('guards PUT behind Business tier and returns saved rules payload', async () => {
    const res = await appFor('/api/settings/field-permissions', fieldPermissionsSettingsRoute).request(
      '/api/settings/field-permissions',
      {
        method: 'PUT',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify([
          {
            entity_type: 'invoice',
            field_name: 'total',
            role: 'CONTRACTOR',
            permission: 'hidden',
          },
          {
            entity_type: 'invoice',
            field_name: 'notes',
            role: 'CONTRACTOR',
            permission: 'visible',
          },
        ]),
      },
      {} as AppEnv['Bindings'],
    )

    expect(res.status).toBe(200)
    expect(registeredPermissions).toContain('permissions:write')
    expect(registeredTiers).toContain(TenantTier.BUSINESS)
    expect(mockSaveTenantFieldRules).toHaveBeenCalledWith(
      {},
      'tenant-1',
      'user-1',
      [
        {
          entity_type: 'invoice',
          field_name: 'total',
          role: 'CONTRACTOR',
          permission: 'hidden',
        },
        {
          entity_type: 'invoice',
          field_name: 'notes',
          role: 'CONTRACTOR',
          permission: 'visible',
        },
      ],
    )
    await expect(res.json()).resolves.toEqual([
      {
        entity_type: 'invoice',
        field_name: 'total',
        role: 'CONTRACTOR',
        permission: 'hidden',
      },
    ])
  })
})

describe('field permissions effective route', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockGetTenantFieldRules.mockResolvedValue([
      {
        entityType: 'invoice',
        fieldName: 'total',
        role: 'CONTRACTOR',
        permission: 'hidden',
      },
      {
        entityType: 'invoice',
        fieldName: 'notes',
        role: 'CONTRACTOR',
        permission: 'read_only',
      },
      {
        entityType: 'invoice',
        fieldName: 'vat_amount',
        role: 'ADMIN',
        permission: 'hidden',
      },
    ])
  })

  it('returns only non-visible rules for the current role', async () => {
    const res = await appFor('/api/field-permissions/effective', fieldPermissionsEffectiveRoute).request(
      '/api/field-permissions/effective',
      undefined,
      {} as AppEnv['Bindings'],
    )

    expect(res.status).toBe(200)
    await expect(res.json()).resolves.toEqual({
      invoice: {
        total: 'hidden',
        notes: 'read_only',
      },
    })
  })
})
