import {
  buildArweaveDataUrl,
  sha256HexFromArrayBuffer,
  validateArweaveTxId,
  validateSha256Hex,
} from './arweave-file-service.js';

const DB_NAME = 'shine-ui-avatar-cache';
const DB_VERSION = 1;
const STORE = 'avatars';
const MAX_ITEMS = 200;
const MAX_BYTES = 50 * 1024 * 1024;

function openDb() {
  return new Promise((resolve, reject) => {
    if (typeof indexedDB === 'undefined') {
      reject(new Error('IndexedDB недоступен'));
      return;
    }
    const request = indexedDB.open(DB_NAME, DB_VERSION);
    request.onupgradeneeded = () => {
      const db = request.result;
      if (!db.objectStoreNames.contains(STORE)) {
        db.createObjectStore(STORE, { keyPath: 'txId' });
      }
    };
    request.onsuccess = () => resolve(request.result);
    request.onerror = () => reject(request.error || new Error('Ошибка открытия IndexedDB'));
  });
}

async function withStore(mode, runner) {
  const db = await openDb();
  try {
    return await new Promise((resolve, reject) => {
      const tx = db.transaction(STORE, mode);
      const store = tx.objectStore(STORE);
      const result = runner(store, tx, resolve, reject);
      if (result !== undefined) resolve(result);
      tx.onerror = () => reject(tx.error || new Error('Ошибка транзакции IndexedDB'));
    });
  } finally {
    db.close();
  }
}

async function getRecord(txId) {
  return withStore('readonly', (store, _tx, resolve, reject) => {
    const req = store.get(txId);
    req.onsuccess = () => resolve(req.result || null);
    req.onerror = () => reject(req.error || new Error('Ошибка чтения кэша аватарки'));
  });
}

async function putRecord(record) {
  return withStore('readwrite', (store, tx, resolve, reject) => {
    store.put(record);
    tx.oncomplete = () => resolve(true);
    tx.onerror = () => reject(tx.error || new Error('Ошибка записи кэша аватарки'));
  });
}

async function verifyBlobSha256(blob, expectedSha256Hex) {
  const expected = String(expectedSha256Hex || '').trim().toLowerCase();
  if (!validateSha256Hex(expected)) return true;
  if (!(blob instanceof Blob)) return false;
  const actual = await sha256HexFromArrayBuffer(await blob.arrayBuffer());
  return actual === expected;
}

async function getAllRecords() {
  return withStore('readonly', (store, _tx, resolve, reject) => {
    const req = store.getAll();
    req.onsuccess = () => resolve(Array.isArray(req.result) ? req.result : []);
    req.onerror = () => reject(req.error || new Error('Ошибка чтения списка кэша аватарок'));
  });
}

async function deleteRecords(keys) {
  if (!Array.isArray(keys) || !keys.length) return;
  await withStore('readwrite', (store, tx, resolve, reject) => {
    keys.forEach((key) => store.delete(key));
    tx.oncomplete = () => resolve(true);
    tx.onerror = () => reject(tx.error || new Error('Ошибка очистки кэша аватарок'));
  });
}

async function ensureCacheLimits() {
  const records = await getAllRecords();
  if (!records.length) return;

  let totalBytes = 0;
  records.forEach((row) => {
    totalBytes += Number(row?.sizeBytes || row?.blob?.size || 0);
  });
  if (records.length <= MAX_ITEMS && totalBytes <= MAX_BYTES) return;

  const ordered = [...records].sort((a, b) => Number(a?.cachedAtMs || 0) - Number(b?.cachedAtMs || 0));
  const deleteKeys = [];
  let keepCount = records.length;
  let keepBytes = totalBytes;
  for (let i = 0; i < ordered.length; i += 1) {
    if (keepCount <= MAX_ITEMS && keepBytes <= MAX_BYTES) break;
    const row = ordered[i];
    deleteKeys.push(String(row?.txId || ''));
    keepCount -= 1;
    keepBytes -= Number(row?.sizeBytes || row?.blob?.size || 0);
  }

  await deleteRecords(deleteKeys.filter(Boolean));
}

async function fetchAvatarBlob({ gateway, txId }) {
  const url = buildArweaveDataUrl({ gateway, txId });
  const response = await fetch(url, { method: 'GET' });
  if (!response.ok) {
    throw new Error(`Не удалось загрузить аватар (${response.status} ${response.statusText})`);
  }
  const sourceBlob = await response.blob();
  const sourceType = String(sourceBlob?.type || '').toLowerCase();
  if (sourceType.startsWith('image/')) {
    return sourceBlob;
  }

  const bytes = new Uint8Array(await sourceBlob.arrayBuffer());
  const detectedType = detectImageMime(bytes);
  if (detectedType) {
    return new Blob([bytes], { type: detectedType });
  }

  // Старые аватары могли быть загружены без корректного Content-Type.
  // Возвращаем исходный blob, чтобы браузер попробовал декодировать сам.
  return sourceBlob;
}

function detectImageMime(bytes) {
  if (!(bytes instanceof Uint8Array) || bytes.length < 12) return '';
  if (
    bytes[0] === 0x89
    && bytes[1] === 0x50
    && bytes[2] === 0x4e
    && bytes[3] === 0x47
    && bytes[4] === 0x0d
    && bytes[5] === 0x0a
    && bytes[6] === 0x1a
    && bytes[7] === 0x0a
  ) return 'image/png';
  if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) return 'image/jpeg';
  if (
    bytes[0] === 0x52
    && bytes[1] === 0x49
    && bytes[2] === 0x46
    && bytes[3] === 0x46
    && bytes[8] === 0x57
    && bytes[9] === 0x45
    && bytes[10] === 0x42
    && bytes[11] === 0x50
  ) return 'image/webp';
  return '';
}

async function getBlobFromCacheOrGateway({ gateway, txId, expectedSha256Hex = '' }) {
  const expected = String(expectedSha256Hex || '').trim().toLowerCase();
  try {
    const cached = await getRecord(txId);
    if (cached?.blob instanceof Blob) {
      if (!validateSha256Hex(expected)) return cached.blob;
      const cacheSha = String(cached?.sha256Hex || '').trim().toLowerCase();
      if (cacheSha && cacheSha === expected) {
        return cached.blob;
      }
      const ok = await verifyBlobSha256(cached.blob, expected);
      if (ok) {
        return cached.blob;
      }
      // кэш повреждён или не совпадает с ожидаемым хэшем: удаляем и перекачиваем
      await deleteRecords([txId]);
    }
  } catch {
    // ignore IndexedDB errors and fallback to fetch
  }

  const blob = await fetchAvatarBlob({ gateway, txId });
  if (validateSha256Hex(expected)) {
    const ok = await verifyBlobSha256(blob, expected);
    if (!ok) {
      throw new Error('SHA256_MISMATCH');
    }
  }
  const computedSha256Hex = await sha256HexFromArrayBuffer(await blob.arrayBuffer());
  const record = {
    txId,
    blob,
    contentType: String(blob.type || 'application/octet-stream'),
    sizeBytes: Number(blob.size || 0),
    sha256Hex: computedSha256Hex,
    cachedAtMs: Date.now(),
  };
  try {
    await putRecord(record);
    await ensureCacheLimits();
  } catch {
    // ignore cache write errors
  }
  return blob;
}

export async function getCachedAvatarObjectUrl({ gateway, txId, expectedSha256Hex = '' }) {
  const cleanTxId = String(txId || '').trim();
  if (!validateArweaveTxId(cleanTxId)) {
    throw new Error('Некорректный Transaction ID Arweave');
  }
  const blob = await getBlobFromCacheOrGateway({
    gateway,
    txId: cleanTxId,
    expectedSha256Hex,
  });
  return URL.createObjectURL(blob);
}

export async function clearAvatarCache() {
  await withStore('readwrite', (store, tx, resolve, reject) => {
    store.clear();
    tx.oncomplete = () => resolve(true);
    tx.onerror = () => reject(tx.error || new Error('Ошибка очистки кэша аватарок'));
  });
}
