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

const TENANT_ID = '00000000-0000-4000-8000-000000000001' as TenantId
const USER_ID = '00000000-0000-4000-8000-000000000099' as UserId

const mockGetMeView = vi.fn()
const mockGetUser2FAStatus = vi.fn()
const mockFindPendingToken = vi.fn()

vi.mock('@zync/db/queries', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@zync/db/queries')>()
  return {
    ...actual,
    createDb: vi.fn(() => ({})),
    getMeView: (...args: unknown[]) => mockGetMeView(...args),
    getUser2FAStatus: (...args: unknown[]) => mockGetUser2FAStatus(...args),
    findPendingToken: (...args: unknown[]) => mockFindPendingToken(...args),
  }
})

vi.mock('@zync/auth', async (importOriginal) => {
  const actual = await importOriginal<typeof import('@zync/auth')>()
  return {
    ...actual,
    hashToken: vi.fn(async (value: string) => `hash:${value}`),
  }
})

import { meRoute } from '../src/routes/auth/me'
import { twoFactorEnrollPublicRoute } from '../src/routes/auth/2fa-enroll'

const mockEnv = {
  DB: { connectionString: 'postgresql://test:test@localhost/test' },
  JWT_SECRET: 'test-jwt-secret',
  KV: {
    get: vi.fn(async () => null),
    put: vi.fn(async () => undefined),
    delete: vi.fn(async () => undefined),
  },
  RATE_LIMITER_AUTH: {
    limit: vi.fn(async () => ({ success: true })),
  },
} as AppEnv['Bindings']

function userSession(overrides: Partial<SessionPayload> = {}): SessionPayload {
  return {
    sub: USER_ID,
    tid: TENANT_ID,
    role: 'OWNER',
    permissions: ['settings:write'],
    tier: TenantTier.FREELANCER,
    type: 'user',
    v: 1,
    enforce_2fa: false,
    two_factor_verified: false,
    exp: Math.floor(Date.now() / 1000) + 3600,
    iat: Math.floor(Date.now() / 1000),
    ...overrides,
  }
}

function meApp(session: SessionPayload) {
  const app = new Hono<AppEnv>()
  app.use('*', async (c, next) => {
    c.set('session', session)
    await next()
  })
  app.route('/', meRoute)
  return app
}

function enrollStartApp(session?: SessionPayload) {
  const app = new Hono<AppEnv>()
  if (session) {
    app.use('*', async (c, next) => {
      c.set('session', session)
      await next()
    })
  }
  app.route('/', twoFactorEnrollPublicRoute)
  return app
}

describe('auth-2fa route regressions', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockGetMeView.mockResolvedValue({
      id: USER_ID,
      email: 'user@example.com',
      name: 'User',
      avatarUrl: null,
      emailVerifiedAt: new Date(),
      tenantId: TENANT_ID,
      tenantSlug: 'acme',
      tenantName: 'Acme',
      tier: TenantTier.FREELANCER,
      roleId: 'role-1',
      role: 'OWNER',
      membershipStatus: 'active',
      onboardingCompleted: true,
      onboardingStep: null,
      permissions: ['settings:write'],
    })
    mockGetUser2FAStatus.mockResolvedValue({
      id: USER_ID,
      twoFactorEnabled: true,
      twoFactorPhone: 'hash',
      twoFactorPhoneSuffix: '44',
    })
    mockFindPendingToken.mockResolvedValue({
      userId: USER_ID,
      tenantId: TENANT_ID,
      expiresAt: new Date(Date.now() + 60_000),
    })
  })

  it('GET /me returns the persisted twoFactorEnabled state', async () => {
    const res = await meApp(userSession()).request('/me', {}, mockEnv)

    expect(res.status).toBe(200)
    const body = (await res.json()) as { twoFactorEnabled: boolean }
    expect(body.twoFactorEnabled).toBe(true)
    expect(mockGetUser2FAStatus).toHaveBeenCalledWith(expect.anything(), USER_ID)
  })

  it('POST /2fa/enroll/start rejects anonymous callers', async () => {
    const res = await enrollStartApp().request(
      '/2fa/enroll/start',
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({}),
      },
      mockEnv,
    )

    expect(res.status).toBe(401)
  })

  it('POST /2fa/enroll/start accepts a pending_2fa_setup token', async () => {
    const res = await enrollStartApp().request(
      '/2fa/enroll/start',
      {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ session_token: 'temp_abc123' }),
      },
      mockEnv,
    )

    expect(res.status).toBe(204)
    expect(mockFindPendingToken).toHaveBeenCalledWith(
      expect.anything(),
      'hash:temp_abc123',
      'pending_2fa_setup',
    )
  })
})
