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

const mockListImportJobs = vi.fn()
const mockGetImportJob = vi.fn()
const mockCreateImportJob = vi.fn()
const mockUpdateImportJobMappings = vi.fn()
const mockListImportJobResults = vi.fn()
const mockDeleteImportJob = vi.fn()
const mockSetImportJobProcessing = vi.fn()

let mockSession: SessionPayload | undefined

vi.mock('../src/middleware/auth', () => ({
  authMiddleware: async (
    c: { set: (key: string, value: unknown) => void },
    next: () => Promise<void>,
  ) => {
    if (mockSession) c.set('session', mockSession)
    c.set('db', {})
    await next()
  },
}))

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

vi.mock('@zync/db/queries', async () => {
  const actual = await vi.importActual<typeof import('@zync/db/queries')>('@zync/db/queries')
  return {
    ...actual,
    listImportJobs: (...args: unknown[]) => mockListImportJobs(...args),
    getImportJob: (...args: unknown[]) => mockGetImportJob(...args),
    createImportJob: (...args: unknown[]) => mockCreateImportJob(...args),
    updateImportJobMappings: (...args: unknown[]) => mockUpdateImportJobMappings(...args),
    setImportJobProcessing: (...args: unknown[]) => mockSetImportJobProcessing(...args),
    listImportJobResults: (...args: unknown[]) => mockListImportJobResults(...args),
    deleteImportJob: (...args: unknown[]) => mockDeleteImportJob(...args),
  }
})

import { importRouter } from '../src/routes/imports/router'

const OWNER_SESSION: SessionPayload = {
  sub: 'user-1' as SessionPayload['sub'],
  tid: 'tenant-1' as SessionPayload['tid'],
  role: 'OWNER',
  permissions: ['invoices:read', 'invoices:write'],
  tier: 'business',
  type: 'user',
  v: 1,
  enforce_2fa: false,
  two_factor_verified: true,
  exp: Math.floor(Date.now() / 1000) + 3600,
  iat: Math.floor(Date.now() / 1000),
}

function createEnv(): AppEnv['Bindings'] {
  return {
    DB: { connectionString: 'postgresql://test:test@localhost/test' },
    JWT_SECRET: 'test-secret',
    STORAGE: {
      get: vi.fn(),
      delete: vi.fn(),
    },
    QUEUE: {
      send: vi.fn(),
    },
  } as unknown as AppEnv['Bindings']
}

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

describe('data import routes', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockSession = OWNER_SESSION
    mockUpdateImportJobMappings.mockResolvedValue({ id: 'job-1' })
    mockSetImportJobProcessing.mockResolvedValue(undefined)
  })

  it('lists import jobs with spec response shape and pagination cursor', async () => {
    mockListImportJobs.mockResolvedValueOnce([
      {
        id: 'job-2',
        type: 'customers',
        status: 'completed',
        created_at: '2026-05-31T14:32:00.000Z',
      },
      {
        id: 'job-1',
        type: 'invoices',
        status: 'pending',
        created_at: '2026-05-31T11:05:00.000Z',
      },
    ])

    const res = await appWithRoute().request('/api/imports?limit=1', { method: 'GET' }, createEnv())

    expect(res.status).toBe(200)
    await expect(res.json()).resolves.toEqual({
      jobs: [
        {
          id: 'job-2',
          type: 'customers',
          status: 'completed',
          created_at: '2026-05-31T14:32:00.000Z',
        },
      ],
      next_cursor: 'job-1',
    })
    expect(mockListImportJobs).toHaveBeenCalledWith({}, 'tenant-1', 2, undefined)
  })

  it('returns the import status payload at the top level', async () => {
    mockGetImportJob.mockResolvedValueOnce({
      id: 'job-1',
      type: 'customers',
      status: 'processing',
      original_filename: 'customers.csv',
      total_rows: 500,
      rows_processed: 335,
      success_count: 320,
      skipped_count: 12,
      error_count: 3,
      created_at: '2026-05-31T14:32:00.000Z',
      completed_at: null,
      error_message: null,
    })

    const res = await appWithRoute().request('/api/imports/job-1', { method: 'GET' }, createEnv())

    expect(res.status).toBe(200)
    await expect(res.json()).resolves.toEqual({
      id: 'job-1',
      type: 'customers',
      status: 'processing',
      original_filename: 'customers.csv',
      total_rows: 500,
      rows_processed: 335,
      success_count: 320,
      skipped_count: 12,
      error_count: 3,
      created_at: '2026-05-31T14:32:00.000Z',
      completed_at: null,
      error_message: null,
    })
  })

  it('starts a pending import and returns spec-shaped processing status', async () => {
    mockGetImportJob.mockResolvedValueOnce({
      id: 'job-1',
      type: 'customers',
      status: 'pending',
      original_filename: 'customers.csv',
    })

    const env = createEnv()
    ;(env.STORAGE as { get: ReturnType<typeof vi.fn> }).get.mockResolvedValueOnce({
      size: 128,
      httpMetadata: { contentType: 'text/csv' },
      text: vi.fn().mockResolvedValue('Full Name,Email Address\nRivka Cohen,rivka@example.com\n'),
    })
    const res = await appWithRoute().request(
      '/api/imports/job-1/start',
      {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          mapping: { 'Full Name': 'name', 'Email Address': 'email' },
        }),
      },
      env,
    )

    expect(res.status).toBe(202)
    await expect(res.json()).resolves.toEqual({
      id: 'job-1',
      status: 'processing',
    })
    expect((env.QUEUE as { send: ReturnType<typeof vi.fn> }).send).toHaveBeenCalledWith(
      expect.objectContaining({
        type: 'import.process',
        import_job_id: 'job-1',
        tenant_id: 'tenant-1',
        column_mapping: { 'Full Name': 'name', 'Email Address': 'email' },
      }),
    )
  })

  it('serves the customers template as a csv attachment', async () => {
    const res = await appWithRoute().request('/api/imports/templates/customers', { method: 'GET' }, createEnv())

    expect(res.status).toBe(200)
    expect(res.headers.get('content-type')).toContain('text/csv')
    expect(res.headers.get('content-disposition')).toBe(
      'attachment; filename="customers-template.csv"',
    )
    await expect(res.text()).resolves.toContain(
      'name,email,phone,company,address_street,address_city,address_zip,address_country,notes',
    )
  })
})
