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

const TENANT_ID = 'tenant-1'
const USER_ID = 'user-1'

const mockGetPortalSettings = vi.fn()
const mockUpsertPortalSettings = vi.fn()
const mockListTenantPortalUsers = vi.fn()
const mockRevokePortalUser = vi.fn()
const mockGetContactById = vi.fn()
const mockGetRoleByName = vi.fn()
const mockCreateInvitation = vi.fn()
const mockAppendCustomerCommunication = vi.fn()
const mockGenerateOpaqueToken = vi.fn(() => 'plain-token')
const mockHashToken = vi.fn(async () => 'token-hash')
const mockSendPortalInvitationEmail = vi.fn(async () => undefined)
const mockSignSignedToken = vi.fn(async () => 'preview-token')
const mockRequirePermission = vi.fn()

let mockSession: Record<string, unknown>

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('../src/middleware/guards', () => ({
  requirePermission: (...args: unknown[]) => mockRequirePermission(...args),
}))

vi.mock('@zync/auth', () => ({
  signSignedToken: (...args: unknown[]) => mockSignSignedToken(...args),
  generateOpaqueToken: (...args: unknown[]) => mockGenerateOpaqueToken(...args),
  hashToken: (...args: unknown[]) => mockHashToken(...args),
}))

vi.mock('@zync/db/queries', () => ({
  getPortalSettings: (...args: unknown[]) => mockGetPortalSettings(...args),
  upsertPortalSettings: (...args: unknown[]) => mockUpsertPortalSettings(...args),
  listTenantPortalUsers: (...args: unknown[]) => mockListTenantPortalUsers(...args),
  revokePortalUser: (...args: unknown[]) => mockRevokePortalUser(...args),
  getContactById: (...args: unknown[]) => mockGetContactById(...args),
  getRoleByName: (...args: unknown[]) => mockGetRoleByName(...args),
  createInvitation: (...args: unknown[]) => mockCreateInvitation(...args),
  appendCustomerCommunication: (...args: unknown[]) => mockAppendCustomerCommunication(...args),
}))

vi.mock('../src/adapters/email-customers', () => ({
  sendPortalInvitationEmail: (...args: unknown[]) => mockSendPortalInvitationEmail(...args),
}))

async function appWithRoute() {
  const { portalSettingsRoute } = await import('../src/routes/settings/portal')
  const app = new Hono<AppEnv>()
  app.route('/api/settings/portal', portalSettingsRoute)
  return app
}

describe('portal settings route', () => {
  beforeEach(() => {
    vi.resetModules()
    vi.clearAllMocks()
    mockSession = {
      type: 'user',
      sub: USER_ID,
      tid: TENANT_ID,
      role: 'OWNER',
      permissions: ['settings:read', 'settings:write'],
    }
    mockRequirePermission.mockImplementation(
      (permission: string) =>
        async (
          c: {
            get: (key: string) => { permissions?: string[] } | undefined
            json: (body: unknown, status: number) => Response
          },
          next: () => Promise<void>,
        ) => {
          const session = c.get('session')
          if (!session?.permissions?.includes(permission)) {
            return c.json({ error: 'Forbidden' }, 403)
          }
          await next()
        },
    )
    mockGetPortalSettings.mockResolvedValue({
      portal_enabled: true,
      portal_access_method: 'magic_link',
      portal_name: null,
      portal_welcome_text: null,
      portal_primary_color_hex: null,
      portal_logo_url: null,
      portal_can_submit_tickets: false,
      portal_can_upload_files: false,
      portal_show_team_members: false,
      portal_visibility: {
        show_invoices: true,
        show_proposals: true,
        show_projects: true,
        show_tickets: false,
        show_files: false,
        show_contracts: false,
        show_time_summary: false,
      },
    })
    mockGetContactById.mockResolvedValue({
      id: 'contact-2',
      customerId: 'customer-2',
      email: 'roni@beta.com',
    })
    mockGetRoleByName.mockResolvedValue({ id: 'viewer-role' })
    mockCreateInvitation.mockResolvedValue({ id: 'invite-1' })
  })

  it('registers the preview, users, revoke, and resend-invite routes with settings permissions', async () => {
    await import('../src/routes/settings/portal')

    expect(mockRequirePermission).toHaveBeenCalledWith('settings:read')
    expect(mockRequirePermission).toHaveBeenCalledWith('settings:write')
  })

  it('resends a portal invite for a known portal user contact', async () => {
    const app = await appWithRoute()
    const res = await app.request('/api/settings/portal/users/contact-2/resend-invite', { method: 'POST' }, {
      JWT_SECRET: 'secret',
      QUEUE: { send: vi.fn() },
    } as AppEnv['Bindings'])

    expect(res.status).toBe(200)
    expect(mockGetContactById).toHaveBeenCalledWith({}, TENANT_ID, 'contact-2')
    expect(mockCreateInvitation).toHaveBeenCalledWith(
      {},
      expect.objectContaining({
        tenantId: TENANT_ID,
        email: 'roni@beta.com',
        roleId: 'viewer-role',
        invitedBy: USER_ID,
      }),
    )
    expect(mockSendPortalInvitationEmail).toHaveBeenCalledWith(
      expect.anything(),
      expect.objectContaining({
        to: 'roni@beta.com',
        contactId: 'contact-2',
        customerId: 'customer-2',
      }),
    )
  })
})
