import { createHash, generateKeyPairSync, sign } from "node:crypto";
import { mkdir, readFile, writeFile } from "node:fs/promises";
import path from "node:path";

interface StoredDeviceIdentity {
  deviceId: string;
  publicKey: string;
  privateKeyPem: string;
}

export interface SignedDeviceProof {
  id: string;
  publicKey: string;
  signature: string;
  signedAt: number;
  nonce: string;
}

interface SignChallengeInput {
  clientId: string;
  clientMode: string;
  role: string;
  scopes: string[];
  token?: string;
  nonce: string;
}

function decodeBase64Url(value: string): Buffer {
  const pad = value.length % 4 === 0 ? "" : "=".repeat(4 - (value.length % 4));
  return Buffer.from((value + pad).replace(/-/g, "+").replace(/_/g, "/"), "base64");
}

export class DeviceIdentityService {
  private readonly userDataPath: string;
  private identity?: StoredDeviceIdentity;

  constructor(userDataPath: string) {
    this.userDataPath = userDataPath;
  }

  async load(): Promise<StoredDeviceIdentity> {
    if (this.identity) {
      return this.identity;
    }

    const filePath = this.getFilePath();
    await mkdir(path.dirname(filePath), { recursive: true });

    try {
      const raw = await readFile(filePath, "utf8");
      this.identity = JSON.parse(raw) as StoredDeviceIdentity;
      return this.identity;
    } catch {
      const created = this.createIdentity();
      this.identity = created;
      await writeFile(filePath, JSON.stringify(created, null, 2), "utf8");
      return created;
    }
  }

  async signConnectChallenge(input: SignChallengeInput): Promise<SignedDeviceProof> {
    const identity = await this.load();
    const signedAt = Date.now();
    const payload = [
      "v2",
      identity.deviceId,
      input.clientId,
      input.clientMode,
      input.role,
      input.scopes.join(","),
      String(signedAt),
      input.token ?? "",
      input.nonce
    ].join("|");

    const signature = sign(null, Buffer.from(payload), identity.privateKeyPem).toString("base64url");

    return {
      id: identity.deviceId,
      publicKey: identity.publicKey,
      signature,
      signedAt,
      nonce: input.nonce
    };
  }

  private createIdentity(): StoredDeviceIdentity {
    const { publicKey, privateKey } = generateKeyPairSync("ed25519");
    const jwk = publicKey.export({ format: "jwk" }) as JsonWebKey;
    if (!jwk.x) {
      throw new Error("Failed to export raw Ed25519 public key.");
    }

    const rawPublicKey = decodeBase64Url(jwk.x);
    const deviceId = createHash("sha256").update(rawPublicKey).digest("hex");

    return {
      deviceId,
      publicKey: Buffer.from(rawPublicKey).toString("base64url"),
      privateKeyPem: privateKey.export({ format: "pem", type: "pkcs8" }).toString()
    };
  }

  private getFilePath(): string {
    return path.join(this.userDataPath, "config", "device-identity.json");
  }
}