import { desc, eq, inArray } from "drizzle-orm"
import type { CertificateDto, CertificateRenewSettingsDto } from "@mmapp/contracts/certificates"
import { db } from "../db/index.js"
import {
  acmeSettings,
  certificateIssueJobs,
  certificateRenewSettings,
  servers,
} from "../db/schema.js"
import { mapRosCertificates } from "./certificate-parse.js"
import { MikrotikClient } from "./mikrotik.js"

const SETTINGS_ID = 1

export type ManagedCertificateTarget = {
  serverId: string
  certName: string
  domainNames: string[]
  keyType: string
  trustStore: string
  finishedAt: string
}

export async function listCertificatesFromServers(): Promise<{
  certificates: CertificateDto[]
  failures: Array<{ serverId: string; serverName?: string; error: string }>
}> {
  const allServers = db.select().from(servers).where(eq(servers.enabled, true)).all()
  const certificates: CertificateDto[] = []
  const failures: Array<{ serverId: string; serverName?: string; error: string }> = []

  await Promise.all(
    allServers.map(async (server) => {
      try {
        const client = MikrotikClient.fromServer(server)
        const rows = await client.getCertificates()
        certificates.push(...mapRosCertificates(server.id, server.name, rows))
      } catch (e) {
        failures.push({
          serverId: String(server.id),
          serverName: server.name,
          error: e instanceof Error ? e.message : String(e),
        })
      }
    }),
  )

  certificates.sort((a, b) => {
    if (a.serverId !== b.serverId) return a.serverId.localeCompare(b.serverId)
    return a.name.localeCompare(b.name, "ru")
  })

  return { certificates, failures }
}

export function getAcmeSettingsPublic() {
  const row = db.select().from(acmeSettings).where(eq(acmeSettings.id, SETTINGS_ID)).limit(1).all()[0]
  if (!row) {
    return {
      directoryUrl: "https://acme-v02.api.letsencrypt.org/directory",
      defaultZoneId: "",
      tokenConfigured: false,
      updatedAt: undefined as string | undefined,
    }
  }
  return {
    directoryUrl: row.directoryUrl,
    defaultZoneId: row.defaultZoneId || undefined,
    tokenConfigured: Boolean(row.cloudflareApiToken?.trim()),
    updatedAt: row.updatedAt,
  }
}

export function getAcmeCloudflareToken(): string {
  const row = db.select().from(acmeSettings).where(eq(acmeSettings.id, SETTINGS_ID)).limit(1).all()[0]
  return row?.cloudflareApiToken?.trim() ?? ""
}

export function getAcmeAccountPrivateKey(): string {
  const row = db.select().from(acmeSettings).where(eq(acmeSettings.id, SETTINGS_ID)).limit(1).all()[0]
  return row?.accountPrivateKey?.trim() ?? ""
}

export function saveAcmeAccountPrivateKey(pem: string) {
  const now = new Date().toISOString()
  db.update(acmeSettings)
    .set({ accountPrivateKey: pem, updatedAt: now })
    .where(eq(acmeSettings.id, SETTINGS_ID))
    .run()
}

export function updateAcmeSettings(input: {
  directoryUrl?: string
  defaultZoneId?: string | null
  cloudflareApiToken?: string | null
}) {
  const row = db.select().from(acmeSettings).where(eq(acmeSettings.id, SETTINGS_ID)).limit(1).all()[0]
  const now = new Date().toISOString()
  const next = {
    directoryUrl: input.directoryUrl?.trim() || row?.directoryUrl || "https://acme-v02.api.letsencrypt.org/directory",
    defaultZoneId:
      input.defaultZoneId === null
        ? ""
        : input.defaultZoneId?.trim() ?? row?.defaultZoneId ?? "",
    cloudflareApiToken:
      input.cloudflareApiToken === null
        ? ""
        : input.cloudflareApiToken?.trim() ?? row?.cloudflareApiToken ?? "",
    updatedAt: now,
  }
  if (row) {
    db.update(acmeSettings).set(next).where(eq(acmeSettings.id, SETTINGS_ID)).run()
  } else {
    db.insert(acmeSettings).values({ id: SETTINGS_ID, accountPrivateKey: "", ...next }).run()
  }
  return getAcmeSettingsPublic()
}

export function createIssueJobRecord(input: {
  id: string
  serverId: string
  certName: string
  domainNames: string[]
  keyType: string
  trustStore: string
  source?: "manual" | "scheduler"
}) {
  const now = new Date().toISOString()
  db.insert(certificateIssueJobs).values({
    id: input.id,
    status: "queued",
    step: "queued",
    source: input.source ?? "manual",
    serverId: input.serverId,
    certName: input.certName,
    domainNames: JSON.stringify(input.domainNames),
    keyType: input.keyType,
    trustStore: input.trustStore,
    requestedAt: now,
  }).run()
}

export function updateIssueJobRecord(
  id: string,
  patch: Partial<{
    status: "queued" | "running" | "done" | "failed"
    step: string
    startedAt: string
    finishedAt: string
    error: string | null
  }>,
) {
  db.update(certificateIssueJobs).set(patch).where(eq(certificateIssueJobs.id, id)).run()
}

export function getIssueJobRecord(id: string) {
  return db.select().from(certificateIssueJobs).where(eq(certificateIssueJobs.id, id)).limit(1).all()[0] ?? null
}

export function toIssueJobDto(row: NonNullable<ReturnType<typeof getIssueJobRecord>>) {
  let domainNames: string[] = []
  try {
    const parsed = JSON.parse(row.domainNames) as unknown
    if (Array.isArray(parsed)) domainNames = parsed.map(String)
  } catch {
    domainNames = []
  }
  return {
    id: row.id,
    status: row.status,
    step: row.step as "queued" | "acme_order" | "dns_challenge" | "finalize" | "import" | "cleanup" | "done",
    serverId: row.serverId,
    certName: row.certName,
    domainNames,
    requestedAt: row.requestedAt,
    startedAt: row.startedAt ?? undefined,
    finishedAt: row.finishedAt ?? undefined,
    error: row.error ?? undefined,
  }
}

export function getServerRowByIdString(serverId: string) {
  const id = Number.parseInt(serverId, 10)
  if (!Number.isFinite(id)) return null
  return db.select().from(servers).where(eq(servers.id, id)).limit(1).all()[0] ?? null
}

function getCertificateRenewSettingsRow() {
  return db.select().from(certificateRenewSettings).where(eq(certificateRenewSettings.id, SETTINGS_ID)).limit(1).all()[0]
    ?? {
      id: SETTINGS_ID,
      enabled: true,
      intervalSec: 21600,
      renewBeforeDays: 30,
      lastCollectedAt: null,
      lastDurationMs: null,
      lastError: null,
      updatedAt: new Date().toISOString(),
    }
}

export function getCertificateRenewSettings(): CertificateRenewSettingsDto {
  const row = getCertificateRenewSettingsRow()
  return {
    enabled: row.enabled,
    intervalSec: row.intervalSec,
    renewBeforeDays: row.renewBeforeDays,
    lastCollectedAt: row.lastCollectedAt ?? null,
    lastDurationMs: row.lastDurationMs ?? null,
    lastError: row.lastError ?? null,
    updatedAt: row.updatedAt,
  }
}

export function updateCertificateRenewSettings(patch: {
  enabled?: boolean
  intervalSec?: number
  renewBeforeDays?: number
}) {
  const prev = getCertificateRenewSettingsRow()
  const now = new Date().toISOString()
  const next = {
    enabled: patch.enabled ?? prev.enabled,
    intervalSec: patch.intervalSec ?? prev.intervalSec,
    renewBeforeDays: patch.renewBeforeDays ?? prev.renewBeforeDays,
    updatedAt: now,
  }
  if (db.select().from(certificateRenewSettings).where(eq(certificateRenewSettings.id, SETTINGS_ID)).limit(1).all()[0]) {
    db.update(certificateRenewSettings).set(next).where(eq(certificateRenewSettings.id, SETTINGS_ID)).run()
  } else {
    db.insert(certificateRenewSettings).values({ id: SETTINGS_ID, ...next }).run()
  }
  return getCertificateRenewSettings()
}

export function touchCertificateRenewRunMeta(patch: {
  lastCollectedAt?: string
  lastDurationMs?: number
  lastError?: string | null
}) {
  const prev = getCertificateRenewSettingsRow()
  db.update(certificateRenewSettings).set({
    lastCollectedAt: patch.lastCollectedAt ?? prev.lastCollectedAt,
    lastDurationMs: patch.lastDurationMs ?? prev.lastDurationMs,
    lastError: patch.lastError === undefined ? prev.lastError : patch.lastError,
    updatedAt: new Date().toISOString(),
  }).where(eq(certificateRenewSettings.id, SETTINGS_ID)).run()
}

export function listManagedCertificateTargets(): ManagedCertificateTarget[] {
  const rows = db.select().from(certificateIssueJobs)
    .where(eq(certificateIssueJobs.status, "done"))
    .orderBy(desc(certificateIssueJobs.finishedAt), desc(certificateIssueJobs.requestedAt))
    .all()

  const byKey = new Map<string, ManagedCertificateTarget>()
  for (const row of rows) {
    const key = `${row.serverId}::${row.certName}`
    if (byKey.has(key)) continue
    let domainNames: string[] = []
    try {
      const parsed = JSON.parse(row.domainNames) as unknown
      if (Array.isArray(parsed)) domainNames = parsed.map(String)
    } catch {
      domainNames = []
    }
    byKey.set(key, {
      serverId: row.serverId,
      certName: row.certName,
      domainNames,
      keyType: row.keyType,
      trustStore: row.trustStore,
      finishedAt: row.finishedAt ?? row.requestedAt,
    })
  }
  return [...byKey.values()]
}

export function hasActiveIssueJob(serverId: string, certName: string): boolean {
  const row = db.select().from(certificateIssueJobs)
    .where(inArray(certificateIssueJobs.status, ["queued", "running"]))
    .orderBy(desc(certificateIssueJobs.requestedAt))
    .all()
    .find((r) => r.serverId === serverId && r.certName === certName)
  return Boolean(row)
}
