const DEFAULT_ARWEAVE_GATEWAY = 'https://arweave.net';
const WINSTON_PER_AR = 1_000_000_000_000n;
const MAX_AVATAR_SOURCE_BYTES = 10 * 1024 * 1024;
const MAX_AVATAR_SIDE_PX = 768;
const AVATAR_QUALITY = 0.86;
const TX_ID_RE = /^[A-Za-z0-9_-]{43}$/;
const SHA256_HEX_RE = /^[A-Fa-f0-9]{64}$/;

let arweaveLibPromise = null;

function normalizeGateway(rawGateway) {
  const raw = String(rawGateway || '').trim();
  if (!raw) return DEFAULT_ARWEAVE_GATEWAY;
  let parsed;
  try {
    parsed = new URL(raw);
  } catch {
    throw new Error('Некорректный Arweave gateway URL');
  }
  if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
    throw new Error('Arweave gateway должен использовать http или https');
  }
  const normalized = `${parsed.protocol}//${parsed.host}${parsed.pathname}`.replace(/\/+$/g, '');
  return normalized || DEFAULT_ARWEAVE_GATEWAY;
}

function parseGatewayForArweaveInit(gateway) {
  let parsed;
  try {
    parsed = new URL(gateway);
  } catch {
    throw new Error('Некорректный Arweave gateway URL');
  }
  const protocol = parsed.protocol.replace(':', '');
  if (protocol !== 'http' && protocol !== 'https') {
    throw new Error('Arweave gateway должен использовать http или https');
  }
  const port = parsed.port
    ? Number(parsed.port)
    : (protocol === 'https' ? 443 : 80);
  return {
    protocol,
    host: parsed.hostname,
    port,
  };
}

async function loadArweaveLib() {
  if (!arweaveLibPromise) {
    arweaveLibPromise = import('https://esm.sh/arweave@1.15.7?bundle');
  }
  return arweaveLibPromise;
}

function toArFromWinston(winston) {
  return Number(BigInt(winston)) / Number(WINSTON_PER_AR);
}

function blobToFile(blob, name, type) {
  const effectiveType = String(type || blob.type || 'application/octet-stream');
  if (typeof File === 'function') {
    return new File([blob], name, { type: effectiveType });
  }
  return Object.assign(blob, { name, lastModified: Date.now(), type: effectiveType });
}

async function createBitmapWithFallback(file) {
  if (typeof createImageBitmap === 'function') {
    try {
      return await createImageBitmap(file);
    } catch {
      // continue with <img> fallback
    }
  }

  const url = URL.createObjectURL(file);
  try {
    const image = await new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = () => resolve(img);
      img.onerror = () => reject(new Error('Не удалось прочитать изображение'));
      img.src = url;
    });
    return image;
  } finally {
    URL.revokeObjectURL(url);
  }
}

function canvasToBlob(canvas, type, quality) {
  return new Promise((resolve) => {
    canvas.toBlob((blob) => resolve(blob), type, quality);
  });
}

function bytesToHex(bytes) {
  return Array.from(bytes, (item) => item.toString(16).padStart(2, '0')).join('');
}

export function validateArweaveTxId(txId) {
  const value = String(txId || '').trim();
  return TX_ID_RE.test(value);
}

export function validateSha256Hex(sha256Hex) {
  const value = String(sha256Hex || '').trim();
  return SHA256_HEX_RE.test(value);
}

export async function sha256HexFromArrayBuffer(buffer) {
  if (!(buffer instanceof ArrayBuffer)) throw new Error('Некорректные данные для SHA256');
  const digest = await crypto.subtle.digest('SHA-256', buffer);
  return bytesToHex(new Uint8Array(digest));
}

export function buildArweaveAvatarValue(txId, sha256Hex = '') {
  const cleanTxId = String(txId || '').trim();
  if (!validateArweaveTxId(cleanTxId)) {
    throw new Error('Некорректный Transaction ID Arweave');
  }
  const cleanSha = String(sha256Hex || '').trim().toLowerCase();
  if (!cleanSha) return `AR:${cleanTxId}`;
  if (!validateSha256Hex(cleanSha)) {
    throw new Error('Некорректный SHA256 хэш аватара');
  }
  return `SHA256:${cleanSha},AR:${cleanTxId}`;
}

export function parseArweaveAvatarValue(value) {
  const raw = String(value || '').trim();
  if (!raw) {
    return { ok: false, network: '', txId: '', sha256Hex: '' };
  }

  const arMatch = raw.match(/(?:^|,)\s*AR:([A-Za-z0-9_-]{43})\s*(?:,|$)/);
  let txId = String(arMatch?.[1] || '').trim();
  if (!txId) {
    // fallback для старых/кривых значений без запятых: "...AR:<txid>..."
    const fallbackAr = raw.match(/AR:([A-Za-z0-9_-]{43})/);
    txId = String(fallbackAr?.[1] || '').trim();
  }
  if (!validateArweaveTxId(txId)) {
    return { ok: false, network: '', txId: '', sha256Hex: '' };
  }

  const shaMatch = raw.match(/(?:^|,)\s*SHA256:([A-Fa-f0-9]{64})\s*(?:,|$)/);
  let sha256Hex = String(shaMatch?.[1] || '').trim().toLowerCase();
  if (!sha256Hex) {
    const fallbackSha = raw.match(/SHA256:([A-Fa-f0-9]{64})/);
    sha256Hex = String(fallbackSha?.[1] || '').trim().toLowerCase();
  }

  return { ok: true, network: 'AR', txId, sha256Hex };
}

export function buildArweaveDataUrl({ gateway, txId }) {
  const normalizedGateway = normalizeGateway(gateway);
  const cleanTxId = String(txId || '').trim();
  if (!validateArweaveTxId(cleanTxId)) {
    throw new Error('Некорректный Transaction ID Arweave');
  }
  return `${normalizedGateway}/${cleanTxId}`;
}

export async function getArweaveUploadPrice({ gateway, byteLength }) {
  const normalizedGateway = normalizeGateway(gateway);
  const bytes = Number(byteLength);
  if (!Number.isInteger(bytes) || bytes <= 0) {
    throw new Error('Некорректный размер файла');
  }

  const response = await fetch(`${normalizedGateway}/price/${bytes}`, { method: 'GET' });
  const raw = String(await response.text()).trim();
  if (!response.ok) {
    throw new Error(`Не удалось получить цену загрузки (${response.status} ${response.statusText})`);
  }
  if (!/^\d+$/.test(raw)) {
    throw new Error('Arweave gateway вернул некорректную цену');
  }

  return {
    gateway: normalizedGateway,
    winston: raw,
    ar: toArFromWinston(raw),
  };
}

export function isSupportedAvatarImageType(type) {
  const clean = String(type || '').trim().toLowerCase();
  return clean === 'image/jpeg' || clean === 'image/png' || clean === 'image/webp';
}

export function validateAvatarSourceFile(file) {
  if (!(file instanceof File)) {
    throw new Error('Выберите файл изображения.');
  }
  if (!isSupportedAvatarImageType(file.type)) {
    throw new Error('Поддерживаются только JPEG, PNG или WebP.');
  }
  if (Number(file.size || 0) > MAX_AVATAR_SOURCE_BYTES) {
    throw new Error('Файл слишком большой. Максимум 10 MB.');
  }
}

export async function prepareAvatarImageFile(file) {
  validateAvatarSourceFile(file);

  let bitmap;
  try {
    bitmap = await createBitmapWithFallback(file);
  } catch {
    throw new Error('Не удалось подготовить изображение.');
  }

  try {
    const originalWidth = Number(bitmap.width || 0);
    const originalHeight = Number(bitmap.height || 0);
    if (!originalWidth || !originalHeight) {
      throw new Error('Не удалось подготовить изображение.');
    }

    const scale = Math.min(1, MAX_AVATAR_SIDE_PX / Math.max(originalWidth, originalHeight));
    const width = Math.max(1, Math.round(originalWidth * scale));
    const height = Math.max(1, Math.round(originalHeight * scale));

    const canvas = document.createElement('canvas');
    canvas.width = width;
    canvas.height = height;
    const ctx = canvas.getContext('2d');
    if (!ctx) {
      throw new Error('Не удалось подготовить изображение.');
    }
    ctx.drawImage(bitmap, 0, 0, width, height);

    let contentType = 'image/webp';
    let fileName = 'avatar.webp';
    let blob = await canvasToBlob(canvas, contentType, AVATAR_QUALITY);
    if (!blob) {
      contentType = 'image/jpeg';
      fileName = 'avatar.jpg';
      blob = await canvasToBlob(canvas, contentType, AVATAR_QUALITY);
    }
    if (!blob) {
      throw new Error('Не удалось подготовить изображение.');
    }

    const optimizedFile = blobToFile(blob, fileName, contentType);
    const optimizedArrayBuffer = await optimizedFile.arrayBuffer();
    const sha256Hex = await sha256HexFromArrayBuffer(optimizedArrayBuffer);
    return {
      file: optimizedFile,
      originalSizeBytes: Number(file.size || 0),
      optimizedSizeBytes: Number(optimizedFile.size || 0),
      originalWidth,
      originalHeight,
      width,
      height,
      contentType,
      sha256Hex,
    };
  } catch (error) {
    if (error instanceof Error) throw error;
    throw new Error('Не удалось подготовить изображение.');
  } finally {
    if (bitmap && typeof bitmap.close === 'function') {
      bitmap.close();
    }
  }
}

export async function uploadArweaveFile({ gateway, jwk, file, tags = [] }) {
  if (!jwk || typeof jwk !== 'object') {
    throw new Error('Arweave-кошелёк не инициализирован.');
  }
  if (!(file instanceof File)) {
    throw new Error('Выберите файл изображения.');
  }

  const normalizedGateway = normalizeGateway(gateway);
  const { host, port, protocol } = parseGatewayForArweaveInit(normalizedGateway);
  const data = await file.arrayBuffer();

  const moduleRef = await loadArweaveLib();
  const Arweave = moduleRef?.default || moduleRef;
  const arweave = Arweave.init({ host, port, protocol });

  const tx = await arweave.createTransaction({ data }, jwk);
  tx.addTag('Content-Type', String(file.type || 'application/octet-stream'));
  tx.addTag('App-Name', 'SHiNE');
  tx.addTag('SHiNE-Type', 'avatar');
  const extraTags = Array.isArray(tags) ? tags : [];
  const profileLoginTag = extraTags.find((item) => String(item?.name || '').trim() === 'SHiNE-Profile-Login');
  const profileLogin = String(profileLoginTag?.value || '').trim();
  if (!profileLogin) {
    throw new Error('Не указан логин профиля для тега SHiNE-Profile-Login');
  }
  tx.addTag('SHiNE-Profile-Login', profileLogin);

  await arweave.transactions.sign(tx, jwk);
  const postResult = await arweave.transactions.post(tx);

  return {
    id: tx.id,
    status: Number(postResult?.status || 0),
    statusText: String(postResult?.statusText || ''),
    sizeBytes: Number(file.size || 0),
    contentType: String(file.type || ''),
  };
}
