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

const mockGetAdapterConfig = vi.fn()
const mockListSyncLogs = vi.fn()
const mockListAdapterConfigs = vi.fn()
const mockUpsertAdapterConfig = vi.fn()
const mockRemoveAdapterConfig = vi.fn()
const mockLogSyncEvent = vi.fn()
const mockGetInvoiceAdapterSettings = vi.fn()
const mockUpdateInvoiceAdapterSettings = vi.fn()
const mockGetInvoice = vi.fn()
const mockGetAdapter = vi.fn()
const mockTestConnection = vi.fn()

vi.mock('../src/middleware/auth', () => ({
  authMiddleware: async (c: { set: (key: string, value: unknown) => void }, next: () => Promise<void>) => {
    c.set('session', {
      type: 'user',
      sub: 'user-1',
      tid: 'tenant-1',
      role: 'OWNER',
      permissions: ['settings:read', 'settings:write', 'invoices:write'],
    })
    c.set('db', {})
    await next()
  },
}))

vi.mock('../src/middleware/guards', () => ({
  requirePermission: (permission: string) => {
    return 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()
    }
  },
}))

vi.mock('../src/middleware/require-module-enabled', () => ({
  requireModuleEnabled: () => async (_c: unknown, next: () => Promise<void>) => next(),
}))

vi.mock('../src/middleware/bump-financials-version', () => ({
  bumpFinancialsVersionOnWrite: async (_c: unknown, next: () => Promise<void>) => next(),
}))

vi.mock('@zync/db/queries', async () => {
  const actual = await vi.importActual<Record<string, unknown>>('@zync/db/queries')
  return {
    ...actual,
    getAdapterConfig: (...args: unknown[]) => mockGetAdapterConfig(...args),
    listSyncLogs: (...args: unknown[]) => mockListSyncLogs(...args),
    listAdapterConfigs: (...args: unknown[]) => mockListAdapterConfigs(...args),
    upsertAdapterConfig: (...args: unknown[]) => mockUpsertAdapterConfig(...args),
    removeAdapterConfig: (...args: unknown[]) => mockRemoveAdapterConfig(...args),
    logSyncEvent: (...args: unknown[]) => mockLogSyncEvent(...args),
    getInvoiceAdapterSettings: (...args: unknown[]) => mockGetInvoiceAdapterSettings(...args),
    updateInvoiceAdapterSettings: (...args: unknown[]) => mockUpdateInvoiceAdapterSettings(...args),
    getInvoice: (...args: unknown[]) => mockGetInvoice(...args),
  }
})

vi.mock('../src/routes/invoice-adapters/router', async () => {
  const actual = await vi.importActual<Record<string, unknown>>('../src/routes/invoice-adapters/router')
  return {
    ...actual,
    getAdapter: (...args: unknown[]) => mockGetAdapter(...args),
  }
})

import { invoiceIntegrationsSettingsRoute } from '../src/routes/settings/integrations-invoicing'

function appForSettings() {
  const app = new Hono<AppEnv>()
  app.route('/api/settings/integrations/invoicing', invoiceIntegrationsSettingsRoute)
  return app
}

describe('invoice integrations settings route', () => {
  beforeEach(() => {
    vi.clearAllMocks()
    mockGetInvoiceAdapterSettings.mockResolvedValue({
      adapter: 'morning',
      autoSyncOnTaxIssue: true,
      autoDraftFromRetainer: false,
      autoSendRetainerInvoice: false,
      taskStatusTrigger: null,
    })
    mockListAdapterConfigs.mockResolvedValue([
      { provider: 'morning', configured: true, metadata: { companyName: 'Acme Books' } },
      { provider: 'icount', configured: false, metadata: {} },
      { provider: 'rivhit', configured: false, metadata: {} },
      { provider: 'invoice4u', configured: false, metadata: {} },
      { provider: 'easycount', configured: false, metadata: {} },
    ])
    mockGetAdapterConfig.mockResolvedValue({
      provider: 'morning',
      apiKey: 'stored-key',
      apiSecret: 'stored-secret',
      metadata: { companyName: 'Acme Books' },
    })
    mockGetAdapter.mockReturnValue({
      testConnection: mockTestConnection,
    })
    mockTestConnection.mockResolvedValue({
      companyName: 'Acme Books',
      capabilities: {
        createInvoice: true,
        createCreditNote: true,
      },
    })
    mockListSyncLogs.mockResolvedValue({ items: [], nextCursor: null, total: 0 })
    mockUpdateInvoiceAdapterSettings.mockResolvedValue({
      adapter: 'morning',
      autoSyncOnTaxIssue: true,
      autoDraftFromRetainer: false,
      autoSendRetainerInvoice: false,
      taskStatusTrigger: null,
    })
  })

  it('lists adapter hub cards from GET /', async () => {
    const res = await appForSettings().request('/api/settings/integrations/invoicing')

    expect(res.status).toBe(200)
    await expect(res.json()).resolves.toEqual({
      adapters: [
        {
          adapter_id: 'morning',
          status: 'active',
          last_sync_at: null,
          last_sync_status: null,
          company_name: 'Acme Books',
        },
        {
          adapter_id: 'icount',
          status: 'inactive',
          last_sync_at: null,
          last_sync_status: null,
        },
        {
          adapter_id: 'rivhit',
          status: 'inactive',
          last_sync_at: null,
          last_sync_status: null,
        },
        {
          adapter_id: 'invoice4u',
          status: 'inactive',
          last_sync_at: null,
          last_sync_status: null,
        },
        {
          adapter_id: 'easycount',
          status: 'inactive',
          last_sync_at: null,
          last_sync_status: null,
        },
      ],
    })
  })

  it('tests credentials from POST /:adapterId/test without persisting them', async () => {
    const res = await appForSettings().request(
      '/api/settings/integrations/invoicing/morning/test',
      {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          credentials: {
            apiKey: 'live-key',
            apiSecret: 'live-secret',
          },
        }),
      },
      { INTEGRATION_ENCRYPTION_KEY: 'enc' } as AppEnv['Bindings'],
    )

    expect(res.status).toBe(200)
    await expect(res.json()).resolves.toEqual({
      ok: true,
      company_name: 'Acme Books',
      checks: {
        create_invoice: true,
        create_credit_note: true,
      },
    })
    expect(mockUpsertAdapterConfig).not.toHaveBeenCalled()
    expect(mockGetAdapter).toHaveBeenCalledWith('morning', {
      provider: 'morning',
      apiKey: 'live-key',
      apiSecret: 'live-secret',
      companyId: undefined,
      metadata: {},
    })
  })

  it('connects an adapter and persists automation settings on POST /:adapterId/connect', async () => {
    const res = await appForSettings().request(
      '/api/settings/integrations/invoicing/icount/connect',
      {
        method: 'POST',
        headers: { 'content-type': 'application/json' },
        body: JSON.stringify({
          credentials: {
            apiKey: 'secret-pass',
            companyId: 'cid-1',
            metadata: { accountEmail: 'billing@example.com' },
          },
          sync_on_sent: true,
          sync_on_tax_issued: true,
          sync_credit_notes: false,
        }),
      },
      { INTEGRATION_ENCRYPTION_KEY: 'enc' } as AppEnv['Bindings'],
    )

    expect(res.status).toBe(200)
    expect(mockUpsertAdapterConfig).toHaveBeenCalledWith(
      {},
      'tenant-1',
      'icount',
      { apiKey: 'secret-pass', apiSecret: undefined, companyId: 'cid-1' },
      { accountEmail: 'billing@example.com' },
      'enc',
    )
    expect(mockUpdateInvoiceAdapterSettings).toHaveBeenCalledWith(
      {},
      'tenant-1',
      'user-1',
      expect.objectContaining({
        adapter: 'icount',
        autoSyncOnTaxIssue: true,
      }),
    )
    await expect(res.json()).resolves.toEqual({ ok: true })
  })

  it('returns per-adapter sync logs from GET /:adapterId/sync-logs', async () => {
    mockListSyncLogs.mockResolvedValue({
      items: [
        {
          id: 'log-1',
          tenantId: 'tenant-1',
          provider: 'morning',
          entityType: 'invoice',
          entityId: 'inv-1',
          externalId: 'ext-99',
          direction: 'push',
          status: 'failure',
          errorPayload: { message: 'timeout', response_payload: { code: 'E_TIMEOUT' } },
          createdAt: '2026-07-01T00:00:00.000Z',
        },
      ],
      nextCursor: null,
      total: 1,
    })

    const res = await appForSettings().request(
      '/api/settings/integrations/invoicing/morning/sync-logs?status=error&entity_type=invoice&page=2&per_page=25',
    )

    expect(res.status).toBe(200)
    expect(mockListSyncLogs).toHaveBeenCalledWith(
      {},
      'tenant-1',
      expect.objectContaining({
        provider: 'morning',
        entityType: 'invoice',
        status: 'failure',
        limit: 25,
      }),
    )
    await expect(res.json()).resolves.toEqual({
      items: [
        {
          id: 'log-1',
          created_at: '2026-07-01T00:00:00.000Z',
          entity_type: 'invoice',
          entity_id: 'inv-1',
          entity_number: null,
          direction: 'push',
          status: 'error',
          external_id: 'ext-99',
          error_message: 'timeout',
          request_payload: null,
          response_payload: { code: 'E_TIMEOUT' },
        },
      ],
      page: 2,
      per_page: 25,
      total: 1,
      next_cursor: null,
    })
  })

  it('disconnects an adapter and clears it as active on DELETE /:adapterId', async () => {
    const res = await appForSettings().request(
      '/api/settings/integrations/invoicing/morning',
      { method: 'DELETE' },
    )

    expect(res.status).toBe(200)
    expect(mockRemoveAdapterConfig).toHaveBeenCalledWith({}, 'tenant-1', 'morning')
    expect(mockUpdateInvoiceAdapterSettings).toHaveBeenCalledWith(
      {},
      'tenant-1',
      'user-1',
      expect.objectContaining({
        adapter: null,
      }),
    )
  })
})
