import { and, eq, inArray } from "drizzle-orm";
import { deliveryAttempts, deliveryJobs, pushDevices, type DeliveryJob, type PushDevice } from "@fedeo/push-db";
import { db } from "../db/client.js";
import { decryptSecret } from "../lib/crypto.js";
import { ApnsClient, type ApnsPayload } from "./apns.js";

export type PushCommand = {
  priority: "normal" | "high";
  ttlSeconds: number;
  collapseKey?: string;
  notification?: {
    title?: string;
    body?: string;
  };
  data?: Record<string, unknown>;
};

const apns = new ApnsClient();

export async function deliverJob(job: DeliveryJob, deviceIds: string[], command: PushCommand): Promise<void> {
  const devices = await db
    .select()
    .from(pushDevices)
    .where(and(inArray(pushDevices.centralDeviceId, deviceIds), eq(pushDevices.instanceId, job.instanceId), eq(pushDevices.status, "active")));

  let sent = 0;
  let failed = 0;
  let lastErrorCode: string | null = null;
  let lastErrorMessage: string | null = null;

  for (const device of devices) {
    const result = await deliverToDevice(device, command);
    if (result.ok) {
      sent += 1;
      await db.insert(deliveryAttempts).values({
        deliveryJobId: job.id,
        deviceId: device.id,
        provider: result.provider,
        status: "sent",
        providerMessageId: result.providerMessageId,
        sentAt: new Date(),
      });
    } else {
      failed += 1;
      lastErrorCode = result.code;
      lastErrorMessage = result.message;
      await db.insert(deliveryAttempts).values({
        deliveryJobId: job.id,
        deviceId: device.id,
        provider: result.provider,
        status: "failed",
        errorCode: result.code,
        errorMessage: result.message,
      });

      if (result.disableDevice) {
        await db.update(pushDevices).set({ status: "invalid", disabledAt: new Date(), updatedAt: new Date() }).where(eq(pushDevices.id, device.id));
      }
    }
  }

  const status = failed === 0 ? "completed" : sent > 0 ? "partial" : "failed";
  await db.update(deliveryJobs).set({
    status,
    sentCount: sent,
    failedCount: failed,
    lastErrorCode,
    lastErrorMessage,
    completedAt: new Date(),
    updatedAt: new Date(),
  }).where(eq(deliveryJobs.id, job.id));
}

async function deliverToDevice(device: PushDevice, command: PushCommand): Promise<
  | { ok: true; provider: "apns" | "web_push" | "fcm"; providerMessageId?: string }
  | { ok: false; provider: "apns" | "web_push" | "fcm"; code: string; message: string; disableDevice: boolean }
> {
  if (device.platform === "ios") {
    if (!device.providerTokenEncrypted) {
      return { ok: false, provider: "apns", code: "missing_provider_token", message: "Für das iOS-Gerät ist kein Provider Token gespeichert.", disableDevice: true };
    }
    const payload: ApnsPayload = {
      aps: {
        alert: command.notification ? { title: command.notification.title, body: command.notification.body } : undefined,
        sound: command.notification ? "default" : undefined,
        "content-available": command.notification ? undefined : 1,
      },
      data: command.data,
    };
    const result = await apns.send(decryptSecret(device.providerTokenEncrypted), payload, {
      collapseKey: command.collapseKey,
      priority: command.priority,
      ttlSeconds: command.ttlSeconds,
    });
    if (result.ok) return { ok: true, provider: "apns", providerMessageId: result.providerMessageId };
    return { ok: false, provider: "apns", code: result.code, message: result.message, disableDevice: result.permanent };
  }

  if (device.platform === "android") {
    return { ok: false, provider: "fcm", code: "fcm_not_implemented", message: "FCM ist im Stack vorbereitet, aber noch nicht implementiert.", disableDevice: false };
  }

  return { ok: false, provider: "web_push", code: "web_push_not_implemented", message: "Web Push ist im Stack vorbereitet, aber noch nicht implementiert.", disableDevice: false };
}
