import http2 from "node:http2";
import { createPrivateKey, type KeyObject } from "node:crypto";
import jwt from "jsonwebtoken";
import { env } from "../config/env.js";

export type ApnsPayload = {
  aps: {
    alert?: {
      title?: string;
      body?: string;
    };
    sound?: string;
    badge?: number;
    "content-available"?: 1;
    "mutable-content"?: 1;
  };
  data?: Record<string, unknown>;
};

export type ApnsResult =
  | { ok: true; providerMessageId?: string }
  | { ok: false; code: string; message: string; permanent: boolean };

export class ApnsClient {
  private signingKey?: KeyObject;
  private signingKeyError?: string;

  isConfigured(): boolean {
    return Boolean(env.APNS_TEAM_ID && env.APNS_KEY_ID && env.APNS_PRIVATE_KEY && env.IOS_BUNDLE_ID);
  }

  async send(deviceToken: string, payload: ApnsPayload, options?: { collapseKey?: string; priority?: "normal" | "high"; ttlSeconds?: number }): Promise<ApnsResult> {
    if (!this.isConfigured()) {
      return { ok: false, code: "apns_not_configured", message: "APNs ist nicht vollständig konfiguriert.", permanent: false };
    }

    const signingKey = this.getSigningKey();
    if (!signingKey) {
      return {
        ok: false,
        code: "apns_invalid_private_key",
        message: this.signingKeyError || "APNs Private Key ist ungültig.",
        permanent: false,
      };
    }

    const host = env.APNS_PRODUCTION ? "https://api.push.apple.com" : "https://api.sandbox.push.apple.com";
    const token = jwt.sign(
      { iss: env.APNS_TEAM_ID, iat: Math.floor(Date.now() / 1000) },
      signingKey,
      { algorithm: "ES256", header: { alg: "ES256", kid: env.APNS_KEY_ID } },
    );

    return await new Promise<ApnsResult>((resolve) => {
      const client = http2.connect(host);
      const headers: http2.OutgoingHttpHeaders = {
        ":method": "POST",
        ":path": `/3/device/${deviceToken}`,
        authorization: `bearer ${token}`,
        "apns-topic": env.IOS_BUNDLE_ID,
        "apns-push-type": payload.aps["content-available"] === 1 && !payload.aps.alert ? "background" : "alert",
        "apns-priority": options?.priority === "high" ? "10" : "5",
      };

      if (options?.collapseKey) headers["apns-collapse-id"] = options.collapseKey.slice(0, 64);
      if (options?.ttlSeconds) {
        headers["apns-expiration"] = String(Math.floor(Date.now() / 1000) + options.ttlSeconds);
      }

      const req = client.request(headers);
      const chunks: Buffer[] = [];
      let statusCode = 0;
      let apnsId: string | undefined;

      req.setEncoding("utf8");
      req.on("response", (responseHeaders) => {
        statusCode = Number(responseHeaders[":status"] || 0);
        const rawApnsId = responseHeaders["apns-id"];
        apnsId = Array.isArray(rawApnsId) ? rawApnsId[0] : rawApnsId;
      });
      req.on("data", (chunk) => chunks.push(Buffer.from(chunk)));
      req.on("error", (error) => {
        client.close();
        resolve({ ok: false, code: "apns_transport_error", message: error.message, permanent: false });
      });
      req.on("end", () => {
        client.close();
        if (statusCode >= 200 && statusCode < 300) {
          resolve({ ok: true, providerMessageId: apnsId });
          return;
        }

        const body = Buffer.concat(chunks).toString("utf8");
        const reason = safeReason(body) || `HTTP ${statusCode}`;
        resolve({
          ok: false,
          code: `apns_${reason.toLowerCase()}`,
          message: reason,
          permanent: ["BadDeviceToken", "Unregistered", "DeviceTokenNotForTopic"].includes(reason),
        });
      });
      req.end(JSON.stringify(payload));
    });
  }

  private getSigningKey(): KeyObject | null {
    if (this.signingKey) return this.signingKey;

    try {
      this.signingKey = createPrivateKey(normalizePem(env.APNS_PRIVATE_KEY));
      if (this.signingKey.asymmetricKeyType !== "ec") {
        this.signingKeyError = "APNs Private Key muss ein elliptic-curve Private Key aus App Store Connect sein.";
        this.signingKey = undefined;
        return null;
      }
      return this.signingKey;
    } catch {
      this.signingKeyError = "APNs Private Key konnte nicht gelesen werden. Bitte den .p8 Key mit Zeilenumbrüchen oder escaped \\n hinterlegen.";
      return null;
    }
  }
}

function safeReason(body: string): string | null {
  try {
    const parsed = JSON.parse(body) as { reason?: string };
    return parsed.reason || null;
  } catch {
    return null;
  }
}

function normalizePem(value: string): string {
  const withEscapedNewlines = value.trim().replace(/\\n/g, "\n");
  if (withEscapedNewlines.includes("\n")) return withEscapedNewlines;

  const match = withEscapedNewlines.match(/^(-----BEGIN [^-]+-----)(.+)(-----END [^-]+-----)$/);
  if (!match) return withEscapedNewlines;

  const body = match[2].replace(/\s+/g, "");
  const wrappedBody = body.match(/.{1,64}/g)?.join("\n") || body;
  return `${match[1]}\n${wrappedBody}\n${match[3]}`;
}
