/**
 * Invoice adapter routes — P056 invoices-adapters.
 *
 * GET    /api/invoice-adapters                     — list configured providers
 * PUT    /api/invoice-adapters/:provider            — upsert credentials
 * DELETE /api/invoice-adapters/:provider            — remove credentials
 * POST   /api/invoice-adapters/:provider/test       — test connection (live ping)
 * POST   /api/invoice-adapters/:provider/push/:id   — push a single invoice
 * GET    /api/invoice-adapters/logs                 — paginated sync logs
 *
 * Adapter providers: morning | icount | rivhit | invoice4u | easycount
 * Credentials are stored encrypted via INTEGRATION_ENCRYPTION_KEY.
 * Access: owner/admin only.
 */
import { Hono } from 'hono'
import { z } from 'zod'
import { authMiddleware } from '../../middleware/auth'
import {
  isOwnerAdminOrMemberRole,
  isOwnerOrAdminRole,
} from '../../lib/system-roles'
import {
  listAdapterConfigs,
  getAdapterConfig,
  upsertAdapterConfig,
  removeAdapterConfig,
  logSyncEvent,
  listSyncLogs,
  IL_INVOICE_PROVIDERS,
} from '@zync/db/queries'
import type { ILInvoiceProvider } from '@zync/db/queries'
import { sql } from '@zync/db'
import {
  createMorningAdapter,
  createICountAdapter,
  createRivhitAdapter,
  createInvoice4uAdapter,
  createEasycountAdapter,
} from '../../integrations/invoice-adapters'
import type { AppEnv } from '../../types'

// ── Zod schemas ───────────────────────────────────────────────────────────────

const ProviderParam = z.enum(IL_INVOICE_PROVIDERS)

const UpsertBodySchema = z.object({
  apiKey: z.string().min(1).optional(),
  apiSecret: z.string().min(1).optional(),
  companyId: z.string().min(1).optional(),
  metadata: z.object({
    accountEmail: z.string().email().optional(),
    companyName: z.string().optional(),
  }).optional().default({}),
})

const LogsQuerySchema = z.object({
  provider: z.enum(IL_INVOICE_PROVIDERS).optional(),
  entityType: z.enum(['invoice', 'customer']).optional(),
  entityId: z.string().uuid().optional(),
  status: z.enum(['success', 'failure', 'skipped']).optional(),
  cursor: z.string().optional(),
  limit: z.coerce.number().int().min(1).max(100).optional().default(50),
})

// ── Router ────────────────────────────────────────────────────────────────────

export const invoiceAdapterRouter = new Hono<AppEnv>()

invoiceAdapterRouter.use('*', authMiddleware)

// ── GET / — list configured providers ────────────────────────────────────────

invoiceAdapterRouter.get('/', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  if (!isOwnerOrAdminRole(session.role)) {
    return c.json({ error: 'Forbidden' }, 403)
  }

  const db = c.get('db')
  const configs = await listAdapterConfigs(db, session.tid)
  return c.json({ adapters: configs })
})

// ── PUT /:provider — upsert credentials ──────────────────────────────────────

invoiceAdapterRouter.put('/:provider', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  if (!isOwnerOrAdminRole(session.role)) {
    return c.json({ error: 'Forbidden' }, 403)
  }

  const providerParse = ProviderParam.safeParse(c.req.param('provider'))
  if (!providerParse.success) {
    return c.json({ error: `Unknown provider. Valid: ${IL_INVOICE_PROVIDERS.join(', ')}` }, 400)
  }

  const bodyParse = UpsertBodySchema.safeParse(await c.req.json().catch(() => null))
  if (!bodyParse.success) {
    return c.json({ error: 'Validation error', issues: bodyParse.error.issues }, 400)
  }

  const { apiKey, apiSecret, companyId, metadata } = bodyParse.data
  const db = c.get('db')
  const encKey = c.env.INTEGRATION_ENCRYPTION_KEY

  await upsertAdapterConfig(
    db,
    session.tid,
    providerParse.data,
    { apiKey, apiSecret, companyId },
    {
      ...metadata,
      lastTestedAt: undefined,
    },
    encKey,
  )

  return c.json({ ok: true, provider: providerParse.data })
})

// ── DELETE /:provider — remove credentials ────────────────────────────────────

invoiceAdapterRouter.delete('/:provider', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  if (!isOwnerOrAdminRole(session.role)) {
    return c.json({ error: 'Forbidden' }, 403)
  }

  const providerParse = ProviderParam.safeParse(c.req.param('provider'))
  if (!providerParse.success) {
    return c.json({ error: 'Unknown provider' }, 400)
  }

  const db = c.get('db')
  await removeAdapterConfig(db, session.tid, providerParse.data)
  return c.json({ ok: true })
})

// ── POST /:provider/test — test connection ────────────────────────────────────

invoiceAdapterRouter.post('/:provider/test', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  if (!isOwnerOrAdminRole(session.role)) {
    return c.json({ error: 'Forbidden' }, 403)
  }

  const providerParse = ProviderParam.safeParse(c.req.param('provider'))
  if (!providerParse.success) {
    return c.json({ error: 'Unknown provider' }, 400)
  }

  const db = c.get('db')
  const provider = providerParse.data
  const config = await getAdapterConfig(db, session.tid, provider, c.env.INTEGRATION_ENCRYPTION_KEY)
  if (!config) {
    return c.json({ error: 'Provider not configured' }, 404)
  }

  const start = Date.now()
  try {
    const adapter = getAdapter(provider, config)
    await adapter.testConnection()
    const durationMs = Date.now() - start

    // Update lastTestedAt in metadata
    await upsertAdapterConfig(
      db,
      session.tid,
      provider,
      { apiKey: config.apiKey, apiSecret: config.apiSecret, companyId: config.companyId },
      { ...config.metadata, lastTestedAt: new Date().toISOString() },
      c.env.INTEGRATION_ENCRYPTION_KEY,
    )

    await logSyncEvent(db, {
      tenantId: session.tid,
      provider,
      entityType: 'invoice',
      entityId: '00000000-0000-0000-0000-000000000000',
      direction: 'push',
      status: 'success',
      durationMs,
      triggeredBy: session.sub,
    })

    return c.json({ ok: true, durationMs })
  } catch (err) {
    const durationMs = Date.now() - start
    const message = err instanceof Error ? err.message : 'Connection test failed'

    await logSyncEvent(db, {
      tenantId: session.tid,
      provider,
      entityType: 'invoice',
      entityId: '00000000-0000-0000-0000-000000000000',
      direction: 'push',
      status: 'failure',
      durationMs,
      errorPayload: { message },
      triggeredBy: session.sub,
    })

    return c.json({ ok: false, error: message }, 502)
  }
})

// ── POST /:provider/push/:invoiceId — push single invoice ─────────────────────

invoiceAdapterRouter.post('/:provider/push/:invoiceId', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  if (!isOwnerAdminOrMemberRole(session.role)) {
    return c.json({ error: 'Forbidden' }, 403)
  }

  const providerParse = ProviderParam.safeParse(c.req.param('provider'))
  if (!providerParse.success) {
    return c.json({ error: 'Unknown provider' }, 400)
  }
  const invoiceId = c.req.param('invoiceId')
  if (!invoiceId.match(/^[0-9a-f-]{36}$/i)) {
    return c.json({ error: 'Invalid invoice id' }, 400)
  }

  const db = c.get('db')
  const provider = providerParse.data
  const config = await getAdapterConfig(db, session.tid, provider, c.env.INTEGRATION_ENCRYPTION_KEY)
  if (!config) {
    return c.json({ error: 'Provider not configured' }, 404)
  }

  const start = Date.now()
  try {
    const result = await pushInvoiceToAdapter({
      db,
      tenantId: session.tid,
      actorId: session.sub,
      provider,
      invoiceId,
      config,
    })
    const durationMs = Date.now() - start
    return c.json({ ok: true, externalId: result.externalId, durationMs })
  } catch (err) {
    const durationMs = Date.now() - start
    const message = err instanceof Error ? err.message : 'Push failed'
    const httpStatus = (err as { httpStatus?: number }).httpStatus

    await logSyncEvent(db, {
      tenantId: session.tid,
      provider,
      entityType: 'invoice',
      entityId: invoiceId,
      direction: 'push',
      status: 'failure',
      httpStatus,
      durationMs,
      errorPayload: { message },
      triggeredBy: session.sub,
    })

    return c.json({ ok: false, error: message }, 502)
  }
})

// ── GET /logs — sync logs ─────────────────────────────────────────────────────

invoiceAdapterRouter.get('/logs', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  if (!isOwnerOrAdminRole(session.role)) {
    return c.json({ error: 'Forbidden' }, 403)
  }

  const parseResult = LogsQuerySchema.safeParse(Object.fromEntries(new URL(c.req.url).searchParams))
  if (!parseResult.success) {
    return c.json({ error: 'Invalid query', issues: parseResult.error.issues }, 400)
  }

  const db = c.get('db')
  const page = await listSyncLogs(db, session.tid, parseResult.data)
  return c.json(page)
})

// ── Adapter factory ───────────────────────────────────────────────────────────

import type { InvoiceAdapter } from '../../integrations/invoice-adapters'

export async function pushInvoiceToAdapter(input: {
  db: AppEnv['Variables']['db']
  tenantId: string
  actorId?: string
  provider: ILInvoiceProvider
  invoiceId: string
  config: NonNullable<Awaited<ReturnType<typeof getAdapterConfig>>>
}): Promise<{ provider: ILInvoiceProvider; externalId: string; externalUrl?: string }> {
  const adapter = getAdapter(input.provider, input.config)
  const externalId = await adapter.pushInvoice(input.invoiceId, input.tenantId, input.db)

  await input.db.execute(sql`
    UPDATE invoices
    SET external_id = ${externalId},
        external_provider = ${input.provider},
        updated_at = now()
    WHERE tenant_id = ${input.tenantId}::uuid
      AND id = ${input.invoiceId}::uuid
  `)

  await logSyncEvent(input.db, {
    tenantId: input.tenantId,
    provider: input.provider,
    entityType: 'invoice',
    entityId: input.invoiceId,
    externalId,
    direction: 'push',
    status: 'success',
    triggeredBy: input.actorId,
  })

  return { provider: input.provider, externalId }
}

export function getAdapter(
  provider: ILInvoiceProvider,
  config: NonNullable<Awaited<ReturnType<typeof getAdapterConfig>>>,
): InvoiceAdapter {
  switch (provider) {
    case 'morning':    return createMorningAdapter(config)
    case 'icount':     return createICountAdapter(config)
    case 'rivhit':     return createRivhitAdapter(config)
    case 'invoice4u':  return createInvoice4uAdapter(config)
    case 'easycount':  return createEasycountAdapter(config)
  }
}
