import { registerUserOnSolana as registerUserOnSolanaShared } from './shine-user-pda-service.js';
import {
  SHINE_USERS_PROGRAM_ID,
  SHINE_LOGIN_GUARD_PROGRAM_ID,
} from '../solana-programs.js';

const CLASSIFY_LOGIN_INSTRUCTION_TAG = 1;
const PRECHECK_SIM_PAYER = 'FUc28vNixp7F3nnkpGVt6nuJbgvJ4429v4B5wS52Df6P';
const SHINE_USERS_USER_PDA_SEED_PREFIX = 'user_login=';

let solanaLibPromise = null;
function loadSolanaLib() {
  if (!solanaLibPromise) solanaLibPromise = import('https://esm.sh/@solana/web3.js@1.98.4?bundle');
  return solanaLibPromise;
}

function pushU32LE(buf, v) {
  const n = v >>> 0;
  buf.push(n & 0xFF, (n >> 8) & 0xFF, (n >> 16) & 0xFF, (n >> 24) & 0xFF);
}

class BorshBuf {
  constructor() { this._b = []; }
  u8(v) { this._b.push(v & 0xFF); }
  u32(v) { pushU32LE(this._b, v); }
  str(s) {
    const enc = new TextEncoder().encode(s);
    this.u32(enc.length);
    for (const x of enc) this._b.push(x);
  }
  raw(bytes) { for (const x of bytes) this._b.push(x); }
  result() { return new Uint8Array(this._b); }
}

function serializeClassifyLoginArgs(login) {
  const b = new BorshBuf();
  b.u8(CLASSIFY_LOGIN_INSTRUCTION_TAG);
  b.str(String(login || ''));
  return b.result();
}

function decodeU32FromB64(rawB64) {
  const bytes = Uint8Array.from(atob(rawB64), (ch) => ch.charCodeAt(0));
  if (bytes.length < 4) throw new Error('LOGIN_GUARD_BAD_RETURN_DATA');
  return new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength).getUint32(0, true);
}

function safeJson(value) {
  try {
    return JSON.stringify(value);
  } catch {
    return String(value);
  }
}

export function formatSolanaErrorDetails(error) {
  const parts = [];
  const msg = String(error?.message || error || '').trim();
  if (msg) parts.push(msg);

  const logs = error?.logs || error?.transactionLogs || error?.simulationLogs || error?.data?.logs;
  if (Array.isArray(logs) && logs.length) {
    parts.push(`Logs: ${logs.join(' | ')}`);
  }

  const errObj = error?.value?.err || error?.err || error?.data?.err;
  if (errObj) {
    parts.push(`Err: ${safeJson(errObj)}`);
  }

  if (!parts.length) return 'unknown';
  return parts.join(' :: ');
}

export function isUserAlreadyExistsSolanaError(error) {
  const details = formatSolanaErrorDetails(error);
  return details.includes('UserAlreadyExists')
    || details.includes('custom program error: 0x4')
    || details.includes('"Custom":4')
    || details.includes('"InstructionError":[2,{"Custom":4}]')
    || details.includes('Instruction 2: custom program error: 0x4');
}

export async function precheckLoginClassOnSolana({ login, solanaEndpoint }) {
  const solana = await loadSolanaLib();
  const connection = new solana.Connection(String(solanaEndpoint || ''), 'confirmed');
  const loginGuardProgram = new solana.PublicKey(SHINE_LOGIN_GUARD_PROGRAM_ID);
  const payer = new solana.PublicKey(PRECHECK_SIM_PAYER);
  const ix = new solana.TransactionInstruction({
    programId: loginGuardProgram,
    keys: [],
    data: serializeClassifyLoginArgs(String(login || '').toLowerCase()),
  });
  const { blockhash } = await connection.getLatestBlockhash('confirmed');
  const v0Message = new solana.TransactionMessage({
    payerKey: payer,
    recentBlockhash: blockhash,
    instructions: [ix],
  }).compileToV0Message();
  const tx = new solana.VersionedTransaction(v0Message);

  const sim = await connection.simulateTransaction(tx, {
    commitment: 'confirmed',
    sigVerify: false,
    replaceRecentBlockhash: true,
  });
  if (sim?.value?.err) {
    const simErr = new Error(`LOGIN_GUARD_SIMULATION_FAILED: ${safeJson(sim.value.err)}`);
    simErr.logs = sim?.value?.logs || [];
    simErr.err = sim?.value?.err;
    throw simErr;
  }
  const returnData = sim?.value?.returnData;
  if (!returnData || returnData.programId !== SHINE_LOGIN_GUARD_PROGRAM_ID) {
    throw new Error('LOGIN_GUARD_BAD_RETURN_DATA');
  }
  const classValue = decodeU32FromB64(returnData.data?.[0] || '');
  if (classValue === 0) return { classCode: 0, className: 'free' };
  if (classValue === 1) return { classCode: 1, className: 'premium' };
  if (classValue === 2) return { classCode: 2, className: 'company' };
  return { classCode: classValue, className: 'unknown' };
}

export async function checkLoginExistsOnSolana({ login, solanaEndpoint }) {
  const solana = await loadSolanaLib();
  const connection = new solana.Connection(String(solanaEndpoint || ''), 'confirmed');
  const usersProgram = new solana.PublicKey(SHINE_USERS_PROGRAM_ID);
  const enc = new TextEncoder();
  const loginNorm = String(login || '').trim().toLowerCase();
  if (!loginNorm) {
    throw new Error('EMPTY_LOGIN');
  }
  const [userPda] = solana.PublicKey.findProgramAddressSync(
    [enc.encode(SHINE_USERS_USER_PDA_SEED_PREFIX), enc.encode(loginNorm)],
    usersProgram,
  );
  const ai = await connection.getAccountInfo(userPda, 'confirmed');
  return { exists: !!ai, userPda: userPda.toBase58() };
}

export async function registerUserOnSolana({ login, keyBundle, solanaEndpoint }) {
  return registerUserOnSolanaShared({ login, keyBundle, solanaEndpoint });
}
