import { decrypt, encrypt, generateKey } from "./index.ts";

export interface EncryptionKey {
  id: string;
  secret: string;
  active?: boolean;
  createdAt?: number;
}

export interface EncryptionKeyring {
  active(): EncryptionKey;
  get(id: string): EncryptionKey | undefined;
  keys(): EncryptionKey[];
  rotate(key?: EncryptionKey): Promise<EncryptionKey>;
  remove(id: string): boolean;
}

const KEY_ID = /^[A-Za-z0-9._-]{1,64}$/;

function validateKey(key: EncryptionKey): void {
  if (!KEY_ID.test(key.id)) {
    throw new TypeError(`Invalid encryption key id: ${key.id}`);
  }
  if (!key.secret.trim()) {
    throw new TypeError(`Encryption key '${key.id}' has an empty secret`);
  }
}

export function createKeyring(initial: EncryptionKey[]): EncryptionKeyring {
  const values = new Map<string, EncryptionKey>();
  let activeCount = 0;
  for (const key of initial) {
    validateKey(key);
    if (values.has(key.id)) {
      throw new Error(`WRN-ENCRYPTION-KEYRING-DUPLICATE: ${key.id}`);
    }
    if (key.active) activeCount++;
    values.set(key.id, { ...key });
  }
  if (!values.size) throw new Error("WRN-ENCRYPTION-KEYRING-EMPTY");
  if (activeCount > 1) throw new Error("WRN-ENCRYPTION-KEYRING-MULTIPLE-ACTIVE");

  const activeInternal = (): EncryptionKey => {
    const key = [...values.values()].find((entry) => entry.active) ?? [...values.values()].at(-1);
    if (!key) throw new Error("WRN-ENCRYPTION-ACTIVE-KEY-MISSING");
    return key;
  };

  return {
    active: () => ({ ...activeInternal() }),
    get(id) {
      const key = values.get(id);
      return key ? { ...key } : undefined;
    },
    keys: () => [...values.values()].map((key) => ({ ...key })),
    async rotate(key) {
      const next = key ?? {
        id: `key-${Date.now().toString(36)}`,
        secret: await generateKey(),
        createdAt: Date.now(),
      };
      validateKey(next);
      for (const current of values.values()) current.active = false;
      const stored = { ...next, active: true };
      values.set(stored.id, stored);
      return { ...stored };
    },
    remove(id) {
      if (!values.has(id)) return false;
      if (values.size <= 1) {
        throw new Error("WRN-ENCRYPTION-KEYRING-LAST-KEY");
      }
      if (activeInternal().id === id) {
        throw new Error("WRN-ENCRYPTION-KEYRING-ACTIVE-REMOVE");
      }
      return values.delete(id);
    },
  };
}

/** Versioned payload: `wrn1.<key-id>.<aes-gcm-payload>`. */
export async function seal(plaintext: string, keyring: EncryptionKeyring): Promise<string> {
  const key = keyring.active();
  return `wrn1.${key.id}.${await encrypt(plaintext, key.secret)}`;
}

export async function open(sealed: string, keyring: EncryptionKeyring): Promise<string> {
  const match = /^wrn1\.([A-Za-z0-9._-]{1,64})\.(.+)$/.exec(sealed);
  if (!match) throw new Error("WRN-ENCRYPTION-PAYLOAD-VERSION");
  const key = keyring.get(match[1]!);
  if (!key) throw new Error(`WRN-ENCRYPTION-KEY-NOT-FOUND: ${match[1]}`);
  return decrypt(match[2]!, key.secret);
}

export function sealedKeyId(sealed: string): string | null {
  return /^wrn1\.([A-Za-z0-9._-]{1,64})\./.exec(sealed)?.[1] ?? null;
}

export function needsRotation(sealed: string, keyring: EncryptionKeyring): boolean {
  return sealedKeyId(sealed) !== keyring.active().id;
}
