/**
 * Accountant export routes — accountant-export (wave-15).
 *
 * Mounted at /accountant under the reports router.
 *
 * POST /accountant/movement         → generate movement file → job
 * POST /accountant/form6111         → generate Form 6111 → job
 * GET  /accountant/exports          → list jobs (last 20)
 * GET  /accountant/exports/:id      → job status + signed download URL
 *
 * All endpoints require accountant:export permission.
 * Generation is audited via logAuditEvent (fire-and-forget).
 */
import { Hono } from 'hono'
import { z } from 'zod'
import type { AppEnv } from '../../types'
import { authMiddleware } from '../../middleware/auth'
import { requirePermission } from '../../middleware/guards'
import {
  createDb,
  createAccountantExportJob,
  getAccountantExportJob,
  listInventoryCountRows,
  listAccountantExportJobs,
  updateAccountantExportJobDownloadExpiry,
  logAuditEvent,
} from '@zync/db/queries'
import { generateMovementFile } from '../../reports/movement-file'
import { buildInventoryCountWorkbook, generateForm6111 } from '../../reports/form6111'

type R2BucketWithPresign = R2Bucket & {
  createPresignedUrl?: (
    method: string,
    key: string,
    options: { expiresIn: number },
  ) => Promise<string>
}

async function presignR2Url(
  env: AppEnv['Bindings'],
  r2Key: string,
  expiresIn: number,
): Promise<string> {
  const storage = env.STORAGE as R2BucketWithPresign
  if (typeof storage.createPresignedUrl === 'function') {
    return storage.createPresignedUrl('GET', r2Key, { expiresIn })
  }
  const { AwsClient } = await import('aws4fetch')
  const envAny = env as unknown as Record<string, string>
  const accountId = envAny['CF_ACCOUNT_ID'] ?? ''
  const accessKeyId = envAny['R2_ACCESS_KEY_ID'] ?? ''
  const secretAccessKey = envAny['R2_SECRET_ACCESS_KEY'] ?? ''
  const bucketName = envAny['R2_BUCKET_NAME'] ?? 'zync-storage'
  const endpoint = `https://${accountId}.r2.cloudflarestorage.com`
  const aws = new AwsClient({ accessKeyId, secretAccessKey, region: 'auto', service: 's3' })
  const url = new URL(`${endpoint}/${bucketName}/${r2Key}`)
  url.searchParams.set('X-Amz-Expires', String(expiresIn))
  const presigned = await aws.sign(new Request(url.toString(), { method: 'GET' }), { aws: { signQuery: true } })
  return presigned.url
}

async function resolveJobDownloadUrl(
  env: AppEnv['Bindings'],
  db: ReturnType<typeof createDb>,
  tenantId: string,
  job: {
    id: string
    status: string
    r2Key: string | null
    downloadExpiresAt: Date | null
  },
): Promise<string | null> {
  if (job.status !== 'done' || !job.r2Key) return null

  const now = Date.now()
  const expired = !job.downloadExpiresAt || job.downloadExpiresAt.getTime() < now
  const expiresIn = 24 * 60 * 60

  if (expired) {
    const downloadUrl = await presignR2Url(env, job.r2Key, expiresIn)
    const downloadExpiresAt = new Date(now + expiresIn * 1000)
    await updateAccountantExportJobDownloadExpiry(db, tenantId, job.id, downloadExpiresAt)
    return downloadUrl
  }

  return presignR2Url(env, job.r2Key, expiresIn)
}

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

const MOVEMENT_FORMATS = ['hashavshevet'] as const

const movementJobSchema = z.object({
  periodFrom: z.string().date(),
  periodTo: z.string().date(),
  format: z.enum(MOVEMENT_FORMATS).default('hashavshevet'),
})

const form6111JobSchema = z.object({
  year: z.coerce.number().int().min(2000).max(2100),
})

const inventoryCountQuerySchema = z.object({
  asOf: z.string().date(),
})

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

export const accountantExportRoutes = new Hono<AppEnv>()

accountantExportRoutes.use('*', authMiddleware)
accountantExportRoutes.use('*', requirePermission('accountant:export'))

// POST /accountant/movement
accountantExportRoutes.post('/movement', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  const tenantId = session.tid

  const body = await c.req.json().catch(() => null)
  const parsed = movementJobSchema.safeParse(body)
  if (!parsed.success) {
    return c.json({ error: 'Invalid body', details: parsed.error.flatten() }, 400)
  }

  const { periodFrom, periodTo, format } = parsed.data
  if (periodTo < periodFrom) {
    return c.json({ error: "'periodTo' must be >= 'periodFrom'" }, 400)
  }

  const db = createDb(c.env)
  const job = await createAccountantExportJob(db, {
    tenantId,
    kind: 'movement_file',
    periodFrom,
    periodTo,
    status: 'pending',
    generatedBy: session.sub,
  })

  // Audit — fire-and-forget
  void logAuditEvent(c, {
    tenantId,
    userId: session.sub,
    actorName: undefined,
    actorEmail: undefined,
    eventType: 'accountant_export.movement_file.requested',
    entityType: 'accountant_export_job',
    entityId: job.id,
    entityLabel: `${periodFrom}_${periodTo}`,
    metadata: { periodFrom, periodTo, format },
  })

  // Generate inline (Worker CPU budget); heavy periods should use queue
  c.executionCtx.waitUntil(generateMovementFile(c.env, tenantId, periodFrom, periodTo, job.id, format))

  return c.json({ jobId: job.id, status: job.status }, 202)
})

// POST /accountant/form6111
accountantExportRoutes.post('/form6111', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  const tenantId = session.tid

  const body = await c.req.json().catch(() => null)
  const parsed = form6111JobSchema.safeParse(body)
  if (!parsed.success) {
    return c.json({ error: 'Invalid body', details: parsed.error.flatten() }, 400)
  }

  const year = parsed.data.year
  const periodFrom = `${year}-01-01`
  const periodTo = `${year}-12-31`

  const db = createDb(c.env)
  const job = await createAccountantExportJob(db, {
    tenantId,
    kind: 'form6111',
    periodFrom,
    periodTo,
    status: 'pending',
    generatedBy: session.sub,
  })

  void logAuditEvent(c, {
    tenantId,
    userId: session.sub,
    actorName: undefined,
    actorEmail: undefined,
    eventType: 'accountant_export.form6111.requested',
    entityType: 'accountant_export_job',
    entityId: job.id,
    entityLabel: String(year),
    metadata: { year },
  })

  c.executionCtx.waitUntil(generateForm6111(c.env, tenantId, periodFrom, periodTo, job.id))

  return c.json({ jobId: job.id, status: job.status }, 202)
})

// GET /accountant/exports
accountantExportRoutes.get('/exports', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  const tenantId = session.tid
  const db = createDb(c.env)
  const jobs = await listAccountantExportJobs(db, tenantId)
  const exports = await Promise.all(
    jobs.map(async (job) => ({
      ...job,
      downloadUrl: await resolveJobDownloadUrl(c.env, db, tenantId, job),
    })),
  )
  return c.json({ exports }, 200)
})

// GET /accountant/exports/:id
accountantExportRoutes.get('/exports/:id', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }
  const tenantId = session.tid
  const jobId = c.req.param('id')

  const db = createDb(c.env)
  const job = await getAccountantExportJob(db, tenantId, jobId)
  if (!job) {
    return c.json({ error: 'Not found' }, 404)
  }

  const downloadUrl = await resolveJobDownloadUrl(c.env, db, tenantId, job)

  return c.json({ ...job, downloadUrl }, 200)
})

// GET /accountant/inventory-count/xlsx
accountantExportRoutes.get('/inventory-count/xlsx', async (c) => {
  const session = c.get('session')
  if (!session || session.type !== 'user' || !session.tid) {
    return c.json({ error: 'Unauthorized' }, 401)
  }

  const parsed = inventoryCountQuerySchema.safeParse({
    asOf: c.req.query('asOf'),
  })
  if (!parsed.success) {
    return c.json({ error: 'Invalid query', details: parsed.error.flatten() }, 400)
  }

  const db = createDb(c.env)
  const rows = await listInventoryCountRows(db, session.tid)
  const bytes = await buildInventoryCountWorkbook(rows, parsed.data.asOf)

  return new Response(bytes, {
    headers: {
      'Content-Type': 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
      'Content-Disposition': `attachment; filename="inventory-count-${parsed.data.asOf}.xlsx"`,
    },
  })
})
