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

const mockGetShellLayout = vi.fn()
const mockSetShellLayout = vi.fn()
const mockDeleteShellLayout = vi.fn()
const mockGetShellPreferences = vi.fn()

let mockSession: Record<string, unknown> | null

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', () => ({
  createDb: vi.fn(() => ({})),
  getShellLayout: (...args: unknown[]) => mockGetShellLayout(...args),
  getShellPreferences: (...args: unknown[]) => mockGetShellPreferences(...args),
  setShellLayout: (...args: unknown[]) => mockSetShellLayout(...args),
  deleteShellLayout: (...args: unknown[]) => mockDeleteShellLayout(...args),
}))

async function appWithRoute() {
  const { shellLayoutRoute } = await import('../src/routes/shell-layout')
  const app = new Hono<AppEnv>()
  app.route('/api/shell/layout', shellLayoutRoute)
  return app
}

describe('shell layout route', () => {
  beforeEach(() => {
    vi.resetModules()
    vi.clearAllMocks()
    mockSession = {
      type: 'user',
      sub: '00000000-0000-4000-8000-000000000099',
      tid: '00000000-0000-4000-8000-000000000001',
    }
    mockGetShellLayout.mockResolvedValue(null)
    mockGetShellPreferences.mockResolvedValue({ ui_shell: 'os', force_shell: null })
    mockSetShellLayout.mockResolvedValue({
      payload: { v: 1, writer: 'tab', committedAt: '2026-07-10T00:00:00.000Z', data: { desktopIcons: [], pinnedTaskbar: [], widgets: [], windows: [] } },
      version: 1,
    })
    mockDeleteShellLayout.mockResolvedValue(undefined)
  })

  it('does not expose the legacy path', async () => {
    const app = await appWithRoute()
    expect((await app.request('/api/shell-layout?device=desktop')).status).toBe(404)
  })

  it('returns the saved tenant-scoped layout and shell prefs', async () => {
    const app = await appWithRoute()

    const res = await app.request('/api/shell/layout?device=desktop')

    expect(res.status).toBe(404)
    expect(mockGetShellLayout).toHaveBeenCalledWith(
      {},
      '00000000-0000-4000-8000-000000000099',
      '00000000-0000-4000-8000-000000000001', 'desktop',
    )
  })

  it('replaces the saved layout and updates shell prefs', async () => {
    const app = await appWithRoute()
    const body = {
      payload: { v: 1, writer: 'tab', committedAt: '2026-07-10T00:00:00.000Z', data: { desktopIcons: [], pinnedTaskbar: [], widgets: [], windows: [] } },
      version: 0,
    }

    const res = await app.request('/api/shell/layout?device=desktop', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(body),
    })

    expect(res.status).toBe(200)
    expect(mockSetShellLayout).toHaveBeenCalledWith(
      {},
      '00000000-0000-4000-8000-000000000099',
      '00000000-0000-4000-8000-000000000001',
      'desktop', body.payload, body.version,
    )
    await expect(res.json()).resolves.toEqual({
      payload: body.payload,
      version: 1,
    })
  })

  it('deletes only the saved layout for the active tenant', async () => {
    const app = await appWithRoute()

    const res = await app.request('/api/shell/layout?device=desktop', {
      method: 'DELETE',
    })

    expect(res.status).toBe(204)
    expect(mockDeleteShellLayout).toHaveBeenCalledWith(
      {},
      '00000000-0000-4000-8000-000000000099',
      '00000000-0000-4000-8000-000000000001', 'desktop',
    )
  })

  it('rejects invalid PUT payloads', async () => {
    const app = await appWithRoute()

    const res = await app.request('/api/shell/layout?device=desktop', {
      method: 'PUT',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        version: 0,
        payload: {},
      }),
    })

    expect(res.status).toBe(400)
    expect(mockSetShellLayout).not.toHaveBeenCalled()
  })

  it('rejects malformed JSON without touching persistence', async () => {
    const app = await appWithRoute()
    const res = await app.request('/api/shell/layout?device=desktop', {
      method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: '{',
    })
    expect(res.status).toBe(400)
    expect(mockSetShellLayout).not.toHaveBeenCalled()
  })

  it('requires an authenticated user session with an active tenant', async () => {
    mockSession = { type: 'user', sub: 'user-without-tenant' }
    const app = await appWithRoute()

    const res = await app.request('/api/shell/layout?device=desktop')

    expect(res.status).toBe(401)
    expect(mockGetShellLayout).not.toHaveBeenCalled()
  })

  it('returns a generic 500 for unexpected persistence failures', async () => {
    mockGetShellLayout.mockRejectedValueOnce(new Error('database password leaked'))
    const app = await appWithRoute()

    const res = await app.request('/api/shell/layout?device=desktop')

    expect(res.status).toBe(500)
    await expect(res.json()).resolves.toEqual({ error: 'Internal server error' })
  })
})
