import {
  decryptText,
  deriveDmAesKey,
  encryptText,
  importPublicKeySpkiB64,
} from "./crypto";
import { E2EE_LOCKED_PREVIEW, isE2eeEnvelope } from "./envelope";
import { ensureIdentityKeys, fetchPeerPublicKey } from "./keys";

const dmKeyCache = new Map<string, CryptoKey>();

/** ciphertext → plaintext برای پیام‌های خودمان (وقتی decrypt به‌هر دلیل fail شود) */
const outboundPlainByCipher = new Map<string, string>();

function cacheKey(a: string, b: string): string {
  return [String(a), String(b)].sort().join(":");
}

export function rememberOutboundPlaintext(
  ciphertext: string,
  plaintext: string
): void {
  if (ciphertext && plaintext && isE2eeEnvelope(ciphertext)) {
    outboundPlainByCipher.set(ciphertext, plaintext);
  }
}

export function recallOutboundPlaintext(ciphertext: string): string | null {
  if (!ciphertext) return null;
  return outboundPlainByCipher.get(ciphertext) ?? null;
}

export function normalizeMessageContent(content: unknown): string {
  if (typeof content === "string") return content;
  if (content && typeof content === "object") {
    try {
      return JSON.stringify(content);
    } catch {
      return "";
    }
  }
  if (content == null) return "";
  return String(content);
}

export async function getDmConversationKey(
  myUserId: string,
  peerUserId: string
): Promise<CryptoKey | null> {
  const me = String(myUserId || "").trim();
  const peer = String(peerUserId || "").trim();
  if (!me || !peer) return null;

  const ck = cacheKey(me, peer);
  const cached = dmKeyCache.get(ck);
  if (cached) return cached;

  try {
    const { privateKey } = await ensureIdentityKeys(me);
    const peerPubB64 = await fetchPeerPublicKey(peer);
    if (!peerPubB64) return null;

    const peerPub = await importPublicKeySpkiB64(peerPubB64);
    const key = await deriveDmAesKey(privateKey, peerPub, me, peer);
    dmKeyCache.set(ck, key);
    return key;
  } catch {
    // شبکه/IndexedDB می‌تواند گذرا خطا بدهد — نباید کل صفحه رمزگشایی را خراب کند
    return null;
  }
}

export function clearDmKeyCache(): void {
  dmKeyCache.clear();
}

export async function encryptDmContent(
  myUserId: string,
  peerUserId: string,
  plaintext: string
): Promise<string | null> {
  if (!plaintext) return plaintext;
  if (isE2eeEnvelope(plaintext)) return plaintext;

  const key = await getDmConversationKey(myUserId, peerUserId);
  if (!key) return null;
  const encrypted = await encryptText(key, plaintext);
  rememberOutboundPlaintext(encrypted, plaintext);
  return encrypted;
}

export async function decryptDmContent(
  myUserId: string,
  peerUserId: string,
  content: string
): Promise<string> {
  const raw = normalizeMessageContent(content);
  if (!isE2eeEnvelope(raw)) return raw;

  const remembered = recallOutboundPlaintext(raw);
  if (remembered) return remembered;

  try {
    const key = await getDmConversationKey(myUserId, peerUserId);
    if (!key) return E2EE_LOCKED_PREVIEW;

    const pt = await decryptText(key, raw);
    if (pt != null) {
      rememberOutboundPlaintext(raw, pt);
      return pt;
    }
    return E2EE_LOCKED_PREVIEW;
  } catch {
    // خطای گذرا در رمزگشایی یک پیام نباید بقیه پیام‌های صفحه را قفل نشان دهد
    return E2EE_LOCKED_PREVIEW;
  }
}

export async function decryptDmMessageList<
  T extends {
    content?: string;
    senderId?: string;
    receiverId?: string;
    replyTo?: { content?: string; [k: string]: unknown } | null;
  }
>(
  myUserId: string,
  peerUserId: string,
  messages: T[]
): Promise<T[]> {
  const out: T[] = [];
  for (const msg of messages) {
    // خطای رمزگشایی یک پیام نباید کل صفحه را از حالت رمزگشایی‌شده خارج کند —
    // هر پیام مستقل پردازش می‌شود و در بدترین حالت فقط همان یکی قفل نمایش داده می‌شود
    let next = msg;
    try {
      const content = normalizeMessageContent(msg.content);
      if (content && isE2eeEnvelope(content)) {
        const decrypted = await decryptDmContent(myUserId, peerUserId, content);
        next = { ...next, content: decrypted };
      } else if (typeof msg.content !== "string" && content) {
        next = { ...next, content };
      }
    } catch {
      next = { ...next, content: E2EE_LOCKED_PREVIEW };
    }
    try {
      if (next.replyTo?.content) {
        const replyRaw = normalizeMessageContent(next.replyTo.content);
        if (isE2eeEnvelope(replyRaw)) {
          const replyContent = await decryptDmContent(
            myUserId,
            peerUserId,
            replyRaw
          );
          next = {
            ...next,
            replyTo: { ...next.replyTo, content: replyContent },
          };
        }
      }
    } catch {
      if (next.replyTo) {
        next = { ...next, replyTo: { ...next.replyTo, content: E2EE_LOCKED_PREVIEW } };
      }
    }
    out.push(next);
  }
  return out;
}
