import CryptoJS from "crypto-js";

type JsonRecord = Record<string, unknown>;

export type EncryptedApiMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";

export type EncryptedApiRequestConfig = {
  url: string;
  method?: string;
  headersJson?: string;
  bodyJson?: string;
};

const DEBUG_LOG_PREFIX = "[EncryptedApiClient]";
const ZERO_IV = CryptoJS.enc.Hex.parse("00000000000000000000000000000000");

function parseJsonRecord(input: string | undefined): JsonRecord {
  if (!input?.trim()) return {};
  const parsed = JSON.parse(input);
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
    throw new Error("Headers must be a JSON object.");
  }
  return parsed as JsonRecord;
}

function decodeBase64Utf8(value: string): string {
  try {
    const words = CryptoJS.enc.Base64.parse(value.trim());
    const utf8 = CryptoJS.enc.Utf8.stringify(words);
    return utf8 || value;
  } catch {
    return value;
  }
}

function getKeyCandidates(value: string | undefined): string[] {
  if (!value) return [];
  const raw = value.trim();
  const decoded = decodeBase64Utf8(raw).trim();
  if (!decoded || decoded === raw) return raw ? [raw] : [];
  return [decoded, raw];
}

function padKey(key: string): string {
  return key.padEnd(32, " ");
}

const decryptWithKey = (encryptedData: string, key: string, stage: string) => {
  const keyUtf8 = CryptoJS.enc.Utf8.parse(padKey(key));
  const iv = CryptoJS.enc.Hex.parse('00000000000000000000000000000000'); // Fixed IV
  const decrypted = CryptoJS.AES.decrypt(encryptedData, keyUtf8, {
      iv,
      mode: CryptoJS.mode.CBC,
      padding: CryptoJS.pad.Pkcs7,
  });
  try {
    const plain = decrypted.toString(CryptoJS.enc.Utf8);
    if (!plain) {
      throw new Error("Empty decrypted value (wrong key/order or invalid payload).");
    }
    return plain;
  } catch (error) {
    throw new Error(
      `${stage} decrypt failed: ${error instanceof Error ? error.message : "Malformed UTF-8 data"}`
    );
  }
};

function encryptWithKey(data: unknown, key: string): string {
  const preparedKey = CryptoJS.enc.Utf8.parse(padKey(key));
  const encrypted = CryptoJS.AES.encrypt(JSON.stringify(data), preparedKey, {
    iv: ZERO_IV,
    mode: CryptoJS.mode.CBC,
    padding: CryptoJS.pad.Pkcs7,
  });
  return encrypted.toString();
}

function tryParseJson(value: string): unknown {
  try {
    return JSON.parse(value);
  } catch {
    return value;
  }
}

function getKeys() {
  const key1Raw = import.meta.env.VITE_ENCRYPT_KEY_1 as string | undefined;
  const key2Raw = import.meta.env.VITE_ENCRYPT_KEY_2 as string | undefined;
  const key3Raw = import.meta.env.VITE_ENCRYPT_KEY_3 as string | undefined;
  const key1Candidates = getKeyCandidates(key1Raw);
  const key2Candidates = getKeyCandidates(key2Raw);
  const key3Candidates = getKeyCandidates(key3Raw);
  if (!key1Candidates.length || !key2Candidates.length || !key3Candidates.length) {
    throw new Error("Missing encryption keys. Set VITE_ENCRYPT_KEY_1/2/3.");
  }
  return { key1Candidates, key2Candidates, key3Candidates };
}

export function Decrypt(encryptedText: string): unknown {
  if (!encryptedText?.trim()) return null;
  const { key1Candidates, key2Candidates, key3Candidates } = getKeys();
  const errors: string[] = [];

  // Try both decryption orders with key variants (decoded and raw).
  for (const key1 of key1Candidates) {
    for (const key2 of key2Candidates) {
      for (const key3 of key3Candidates) {
        const orders: Array<{ name: string; keys: [string, string, string] }> = [
          { name: "K3->K2->K1", keys: [key3, key2, key1] },
          { name: "K1->K2->K3", keys: [key1, key2, key3] },
        ];
        for (const order of orders) {
          try {
            const firstDecrypt = decryptWithKey(encryptedText, order.keys[0], `Layer1(${order.name})`);
            const secondDecrypt = decryptWithKey(firstDecrypt, order.keys[1], `Layer2(${order.name})`);
            const finalDecrypt = decryptWithKey(secondDecrypt, order.keys[2], `Layer3(${order.name})`);
            return tryParseJson(finalDecrypt);
          } catch (error) {
            errors.push(error instanceof Error ? error.message : "Unknown decrypt error");
          }
        }
      }
    }
  }

  throw new Error(errors[errors.length - 1] || "Decryption failed for all key/order variants.");
}

export function Encrypt(data: unknown): string {
  const { key1Candidates, key2Candidates, key3Candidates } = getKeys();
  const key1 = key1Candidates[0];
  const key2 = key2Candidates[0];
  const key3 = key3Candidates[0];
  const firstEncrypt = encryptWithKey(data, key1);
  const secondEncrypt = encryptWithKey(firstEncrypt, key2);
  return encryptWithKey(secondEncrypt, key3);
}

function pickEncryptedValue(raw: unknown): string | null {
  if (typeof raw === "string") return raw;
  if (!raw || typeof raw !== "object") return null;

  const data = raw as JsonRecord;
  const directCandidates = ["encryptedData", "encrypted", "cipher", "ciphertext", "payload", "data", "response"];
  for (const key of directCandidates) {
    const value = data[key];
    if (typeof value === "string") return value;
  }

  const nestedCandidates = [data.data, data.payload, data.response, data.result];
  for (const candidate of nestedCandidates) {
    if (candidate && typeof candidate === "object" && !Array.isArray(candidate)) {
      const nested = candidate as JsonRecord;
      for (const key of directCandidates) {
        const value = nested[key];
        if (typeof value === "string") return value;
      }
    }
  }

  return null;
}

export function decodeEncryptedApiResponse(raw: unknown): unknown {
  // Common backend envelope: { status, statusCode, message, data: "<encrypted>" }
  if (raw && typeof raw === "object" && !Array.isArray(raw)) {
    const record = raw as JsonRecord;
    if (typeof record.data === "string" && record.data.trim()) {
      const decrypted = Decrypt(record.data);
      return decrypted;
    }
  }

  const encryptedText = pickEncryptedValue(raw);
  if (!encryptedText) return raw;
  const decrypted = Decrypt(encryptedText);
  return decrypted;
}

export async function fetchEncryptedApiData(config: EncryptedApiRequestConfig): Promise<unknown> {
  if (!config.url?.trim()) throw new Error("API URL is required.");
  const method = (config.method || "GET").toUpperCase() as EncryptedApiMethod;
  const headers = parseJsonRecord(config.headersJson);
  const bodyAllowed = method !== "GET" && method !== "HEAD";
  const hasBody = !!config.bodyJson?.trim();

  const requestInit: RequestInit = {
    method,
    headers: headers as HeadersInit,
  };

  if (bodyAllowed && hasBody) {
    requestInit.body = config.bodyJson as string;
    if (!(requestInit.headers as JsonRecord)["Content-Type"]) {
      (requestInit.headers as JsonRecord)["Content-Type"] = "application/json";
    }
  }

  const res = await fetch(config.url, requestInit);
  if (!res.ok) throw new Error(`API request failed (${res.status})`);

  const contentType = res.headers.get("content-type") || "";
  const raw = contentType.includes("application/json") ? await res.json() : await res.text();
  try {
    const decoded = decodeEncryptedApiResponse(raw);
    return decoded;
  } catch (error) {
    throw error;
  }
}
