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

const mockListInventoryLocations = vi.fn()
const mockCreateInventoryLocation = vi.fn()
const mockUpdateInventoryLocation = vi.fn()
const mockDeleteInventoryLocation = vi.fn()

let mockSession: Record<string, unknown> = {
  type: 'user',
  sub: 'user-1',
  tid: 'tenant-1',
  permissions: ['inventory:manage'],
}

vi.mock('../src/middleware/guards', () => ({
  requirePermission: (permission: string) => {
    return async (
      c: { get: (key: string) => unknown; json: (body: unknown, status: number) => Response },
      next: () => Promise<void>,
    ) => {
      const session = c.get('session') as { permissions?: string[] } | undefined
      if (!session?.permissions?.includes(permission)) {
        return c.json({ error: 'Forbidden' }, 403)
      }
      await next()
    }
  },
}))

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/require-module-enabled', () => ({
  requireModuleEnabled: () => async (_c: unknown, next: () => Promise<void>) => {
    await next()
  },
}))

vi.mock('@zync/db/queries', async () => {
  const actual = await vi.importActual<Record<string, unknown>>('@zync/db/queries')
  class InventoryLocationConflictError extends Error {}

  return {
    ...actual,
    InventoryLocationConflictError,
    listInventoryLocations: (...args: unknown[]) => mockListInventoryLocations(...args),
    createInventoryLocation: (...args: unknown[]) => mockCreateInventoryLocation(...args),
    updateInventoryLocation: (...args: unknown[]) => mockUpdateInventoryLocation(...args),
    deleteInventoryLocation: (...args: unknown[]) => mockDeleteInventoryLocation(...args),
  }
})

import { inventoryRoute } from '../src/routes/inventory'

function appWithRoute() {
  const app = new Hono<AppEnv>()
  app.route('/api/inventory', inventoryRoute)
  return app
}

describe('/api/inventory/locations routes', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockSession = {
      type: 'user',
      sub: 'user-1',
      tid: 'tenant-1',
      permissions: ['inventory:manage'],
    }
  })

  it('lists tenant locations for inventory managers', async () => {
    mockListInventoryLocations.mockResolvedValue([
      { id: 'loc-1', name: 'Main', code: 'MAIN', isDefault: true, isActive: true },
    ])

    const res = await appWithRoute().request('/api/inventory/locations')

    expect(res.status).toBe(200)
    expect(mockListInventoryLocations).toHaveBeenCalledWith({}, 'tenant-1')
    await expect(res.json()).resolves.toEqual({
      locations: [{ id: 'loc-1', name: 'Main', code: 'MAIN', isDefault: true, isActive: true }],
    })
  })

  it('creates a location for inventory managers', async () => {
    mockCreateInventoryLocation.mockResolvedValue({
      id: 'loc-2',
      name: 'Overflow',
      code: 'OVR',
      isDefault: false,
      isActive: true,
    })

    const res = await appWithRoute().request('/api/inventory/locations', {
      method: 'POST',
      headers: { 'content-type': 'application/json' },
      body: JSON.stringify({ name: 'Overflow', code: 'OVR' }),
    })

    expect(res.status).toBe(201)
    expect(mockCreateInventoryLocation).toHaveBeenCalledWith({}, 'tenant-1', {
      name: 'Overflow',
      code: 'OVR',
    })
  })

  it('forbids location reads without inventory:manage', async () => {
    mockSession = {
      type: 'user',
      sub: 'user-1',
      tid: 'tenant-1',
      permissions: ['inventory:read'],
    }

    const res = await appWithRoute().request('/api/inventory/locations')

    expect(res.status).toBe(403)
    await expect(res.json()).resolves.toEqual({ error: 'Forbidden' })
  })

  it('returns conflict when deleting the default location', async () => {
    const { InventoryLocationConflictError } = await import('@zync/db/queries')
    mockDeleteInventoryLocation.mockRejectedValueOnce(
      new (InventoryLocationConflictError as typeof Error)('Cannot delete default location'),
    )

    const res = await appWithRoute().request('/api/inventory/locations/loc-1', {
      method: 'DELETE',
    })

    expect(res.status).toBe(409)
    await expect(res.json()).resolves.toEqual({ error: 'Cannot delete default location' })
  })
})
