import { randomUUID } from "node:crypto"
import type { FastifyPluginAsyncZod } from "@fastify/type-provider-zod"
import {
  certificateIssueRequestSchema,
  putAcmeCloudflareSettingsSchema,
  testAcmeCloudflareSettingsSchema,
} from "@mmapp/contracts/certificates"
import { appendEvent } from "../modules/events/service/events-service.js"
import { issueCertificateWithCloudflareDns, testCloudflareToken } from "../services/acme-cloudflare.js"
import {
  createIssueJobRecord,
  getAcmeCloudflareToken,
  getAcmeSettingsPublic,
  getIssueJobRecord,
  getServerRowByIdString,
  listCertificatesFromServers,
  toIssueJobDto,
  updateAcmeSettings,
  updateIssueJobRecord,
} from "../services/certificates-service.js"

const runningJobs = new Set<string>()

async function runIssueJob(jobId: string) {
  if (runningJobs.has(jobId)) return
  runningJobs.add(jobId)
  const row = getIssueJobRecord(jobId)
  if (!row) {
    runningJobs.delete(jobId)
    return
  }

  const server = getServerRowByIdString(row.serverId)
  if (!server) {
    updateIssueJobRecord(jobId, {
      status: "failed",
      step: "failed",
      finishedAt: new Date().toISOString(),
      error: "Сервер не найден",
    })
    runningJobs.delete(jobId)
    return
  }

  let domainNames: string[] = []
  try {
    domainNames = JSON.parse(row.domainNames) as string[]
  } catch {
    domainNames = []
  }

  const trustStore = row.trustStore.split(",").map((s) => s.trim()).filter(Boolean)
  const startedAt = new Date().toISOString()
  updateIssueJobRecord(jobId, { status: "running", step: "acme_order", startedAt, error: null })

  try {
    await issueCertificateWithCloudflareDns({
      server,
      certName: row.certName,
      domainNames,
      keyType: row.keyType === "ec256" ? "ec256" : "rsa2048",
      trustStore: trustStore.length > 0 ? trustStore : ["www", "api"],
      onStep: (step) => updateIssueJobRecord(jobId, { step }),
    })
    updateIssueJobRecord(jobId, {
      status: "done",
      step: "done",
      finishedAt: new Date().toISOString(),
      error: null,
    })
    appendEvent({
      level: "info",
      eventType: "certificates.issue.done",
      sourceModule: "certificates",
      title: "Сертификат выпущен",
      message: `${row.certName} · ${domainNames.join(", ")} · ${server.name}`,
      entityType: "server",
      entityId: String(server.id),
    })
  } catch (e) {
    updateIssueJobRecord(jobId, {
      status: "failed",
      step: "failed",
      finishedAt: new Date().toISOString(),
      error: e instanceof Error ? e.message : String(e),
    })
    appendEvent({
      level: "warning",
      eventType: "certificates.issue.failed",
      sourceModule: "certificates",
      title: "Ошибка выпуска сертификата",
      message: e instanceof Error ? e.message : String(e),
      entityType: "server",
      entityId: String(server.id),
    })
  } finally {
    runningJobs.delete(jobId)
  }
}

const certificatesRoutes: FastifyPluginAsyncZod = async (app) => {
  app.get("/certificates", async (_req, reply) => {
    return reply.send(await listCertificatesFromServers())
  })

  app.post("/certificates/refresh", async (_req, reply) => {
    return reply.send(await listCertificatesFromServers())
  })

  app.get("/certificates/acme-settings", async (_req, reply) => {
    return reply.send(getAcmeSettingsPublic())
  })

  app.put("/certificates/acme-settings", async (req, reply) => {
    const parsed = putAcmeCloudflareSettingsSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    return reply.send(updateAcmeSettings(parsed.data))
  })

  app.post("/certificates/acme-settings/test", async (req, reply) => {
    const parsed = testAcmeCloudflareSettingsSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const effective = parsed.data.cloudflareApiToken?.trim() || getAcmeCloudflareToken()
    if (!effective) {
      return reply.status(400).send({ error: "Не задан Cloudflare API token" })
    }
    try {
      await testCloudflareToken(effective)
      return reply.send({ ok: true, message: "Cloudflare API доступен" })
    } catch (e) {
      return reply.status(400).send({
        ok: false,
        message: e instanceof Error ? e.message : String(e),
      })
    }
  })

  app.post("/certificates/issue", async (req, reply) => {
    const parsed = certificateIssueRequestSchema.safeParse(req.body ?? {})
    if (!parsed.success) {
      return reply.status(400).send({ error: "Некорректное тело запроса", details: parsed.error.flatten() })
    }
    const serverId = String(parsed.data.serverId)
    const server = getServerRowByIdString(serverId)
    if (!server) return reply.status(404).send({ error: "Сервер не найден" })

    const jobId = randomUUID()
    const trustStore = (parsed.data.trustStore ?? ["www", "api"]).join(",")
    createIssueJobRecord({
      id: jobId,
      serverId,
      certName: parsed.data.certName.trim(),
      domainNames: parsed.data.domainNames.map((d) => d.trim()).filter(Boolean),
      keyType: parsed.data.keyType ?? "rsa2048",
      trustStore,
    })
    queueMicrotask(() => { void runIssueJob(jobId) })
    return reply.send({ jobId })
  })

  app.get("/certificates/issue/:jobId", async (req, reply) => {
    const jobId = String((req.params as { jobId: string }).jobId)
    const row = getIssueJobRecord(jobId)
    if (!row) return reply.status(404).send({ error: "Задача не найдена" })
    return reply.send(toIssueJobDto(row))
  })
}

export default certificatesRoutes
