import { base64ToBytes } from './crypto-utils.js';
import { PublicKey } from './vendor/solana-publickey-bundle.js';

const SOLANA_ENDPOINT_DEFAULT = 'https://api.devnet.solana.com';
const SHINE_USERS_PROGRAM_ID = '3bYrnXwLc56oVPUBAjY8zTMLwHCYq29b5rUMu3b64SQJ';
const SHINE_USERS_USER_PDA_SEED_PREFIX = 'user_login=';
const DEFAULT_SHINE_SERVER_LOGIN = 'shineupme';
const DEFAULT_SHINE_SERVER_ADDRESS = 'shineup.me';

function normalizeHostLike(value) {
  const raw = String(value || '').trim();
  if (!raw) return '';
  try {
    const withScheme = /^[a-z]+:\/\//i.test(raw) ? raw : `https://${raw}`;
    const parsed = new URL(withScheme);
    return String(parsed.host || '').trim().toLowerCase();
  } catch {
    return raw.replace(/^https?:\/\//i, '').replace(/^wss?:\/\//i, '').replace(/\/.*$/, '').trim().toLowerCase();
  }
}

function normalizeServerLogin(value) {
  return String(value || '').trim().toLowerCase();
}

function buildHttpBase(address) {
  const host = normalizeHostLike(address) || DEFAULT_SHINE_SERVER_ADDRESS;
  return `https://${host}`;
}

function buildWsUrl(address) {
  const host = normalizeHostLike(address) || DEFAULT_SHINE_SERVER_ADDRESS;
  return `wss://${host}/ws`;
}

function readU8(bytes, cursorRef) {
  if (cursorRef.value >= bytes.length) throw new Error('Повреждённый формат PDA');
  return bytes[cursorRef.value++];
}

function readBytes(bytes, cursorRef, length) {
  if (cursorRef.value + length > bytes.length) throw new Error('Повреждённый формат PDA');
  const out = bytes.slice(cursorRef.value, cursorRef.value + length);
  cursorRef.value += length;
  return out;
}

function readStrU8(bytes, cursorRef) {
  const length = readU8(bytes, cursorRef);
  return new TextDecoder().decode(readBytes(bytes, cursorRef, length));
}

function parseServerFieldsFromUserPda(dataBytes) {
  const bytes = dataBytes instanceof Uint8Array ? dataBytes : new Uint8Array(dataBytes || []);
  if (bytes.length < 5) throw new Error('Некорректный формат PDA');
  const cursorRef = { value: 0 };
  const magic = new TextDecoder().decode(readBytes(bytes, cursorRef, 5));
  if (magic !== 'SHiNE') throw new Error('Некорректный формат PDA');
  cursorRef.value += 1; // format_major
  cursorRef.value += 1; // format_minor
  cursorRef.value += 2; // record_len
  cursorRef.value += 8; // created_at_ms
  cursorRef.value += 8; // updated_at_ms
  cursorRef.value += 4; // record_number
  cursorRef.value += 32; // prev_record_hash
  readStrU8(bytes, cursorRef); // login
  const blocksCount = readU8(bytes, cursorRef);

  let isServer = false;
  let serverAddress = '';
  let accessServers = [];
  let recoveryKey32 = null;
  let rootKey32 = null;
  let clientKey32 = null;
  let blockchainKey32 = null;
  let blockchainName = '';
  let homeserverSessions = [];

  for (let i = 0; i < blocksCount; i += 1) {
    const blockType = readU8(bytes, cursorRef);
    cursorRef.value += 1; // block_version

    if (blockType === 0 || blockType === 1 || blockType === 2) {
      const key32 = readBytes(bytes, cursorRef, 32);
      if (blockType === 0) recoveryKey32 = key32;
      if (blockType === 1) rootKey32 = key32;
      if (blockType === 2) clientKey32 = key32;
      continue;
    }
    if (blockType === 3) {
      const count = readU8(bytes, cursorRef);
      for (let j = 0; j < count; j += 1) {
        cursorRef.value += 1;
        const currentBlockchainName = readStrU8(bytes, cursorRef);
        const currentBlockchainKey32 = readBytes(bytes, cursorRef, 32);
        if (!blockchainKey32) {
          blockchainKey32 = currentBlockchainKey32;
          blockchainName = currentBlockchainName;
        }
        cursorRef.value += 8 + 8 + 4 + 32 + 64;
        const arPresent = readU8(bytes, cursorRef);
        if (arPresent === 1) readStrU8(bytes, cursorRef);
      }
      continue;
    }
    if (blockType === 30) {
      isServer = readU8(bytes, cursorRef) === 1;
      if (isServer) {
        cursorRef.value += 1; // address_format_type
        cursorRef.value += 1; // address_format_version
        serverAddress = readStrU8(bytes, cursorRef);
        const syncCount = readU8(bytes, cursorRef);
        for (let j = 0; j < syncCount; j += 1) readStrU8(bytes, cursorRef);
      }
      continue;
    }
    if (blockType === 40) {
      const accessCount = readU8(bytes, cursorRef);
      accessServers = [];
      for (let j = 0; j < accessCount; j += 1) accessServers.push(readStrU8(bytes, cursorRef));
      continue;
    }
    if (blockType === 50) {
      cursorRef.value += 1;
      const sessionsCount = readU8(bytes, cursorRef);
      for (let j = 0; j < sessionsCount; j += 1) {
        const sessionType = readU8(bytes, cursorRef);
        const sessionVersion = readU8(bytes, cursorRef);
        const sessionName = readStrU8(bytes, cursorRef);
        const sessionPubKey32 = readBytes(bytes, cursorRef, 32);
        if (sessionType === 100) {
          homeserverSessions.push({
            sessionType,
            sessionVersion,
            sessionName,
            sessionPubKeyBase58: new PublicKey(sessionPubKey32).toBase58(),
            sessionPubKeyB64: `ed25519/${btoa(String.fromCharCode(...sessionPubKey32))}`,
          });
        }
      }
      continue;
    }
    if (blockType === 70) {
      cursorRef.value += 1;
      continue;
    }
    throw new Error(`Неизвестный блок PDA: ${blockType}`);
  }

  return {
    isServer,
    serverAddress: normalizeHostLike(serverAddress),
    accessServers: accessServers.map((value) => normalizeServerLogin(value)).filter(Boolean),
    publicKeys: {
      recoveryKeyBase58: recoveryKey32 ? new PublicKey(recoveryKey32).toBase58() : '',
      rootKeyBase58: rootKey32 ? new PublicKey(rootKey32).toBase58() : '',
      clientKeyBase58: clientKey32 ? new PublicKey(clientKey32).toBase58() : '',
      blockchainKeyBase58: blockchainKey32 ? new PublicKey(blockchainKey32).toBase58() : '',
      blockchainName,
    },
    homeserverSessions,
  };
}

async function fetchUserPda(login, solanaEndpoint = SOLANA_ENDPOINT_DEFAULT) {
  const cleanLogin = normalizeServerLogin(login);
  if (!cleanLogin) throw new Error('Не указан логин для чтения PDA.');
  const usersProgram = new PublicKey(SHINE_USERS_PROGRAM_ID);
  const enc = new TextEncoder();
  const [userPda] = PublicKey.findProgramAddressSync(
    [enc.encode(SHINE_USERS_USER_PDA_SEED_PREFIX), enc.encode(cleanLogin)],
    usersProgram,
  );

  const response = await fetch(String(solanaEndpoint || SOLANA_ENDPOINT_DEFAULT), {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    cache: 'no-store',
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 1,
      method: 'getAccountInfo',
      params: [userPda.toBase58(), { encoding: 'base64', commitment: 'confirmed' }],
    }),
  });
  if (!response.ok) throw new Error('Не удалось прочитать Solana RPC.');
  const json = await response.json();
  const dataB64 = json?.result?.value?.data?.[0];
  if (!dataB64) throw new Error(`PDA не найдена для логина @${cleanLogin}.`);
  return parseServerFieldsFromUserPda(base64ToBytes(dataB64));
}

export async function resolveShineServerByServerLogin(serverLogin, solanaEndpoint = SOLANA_ENDPOINT_DEFAULT) {
  const cleanServerLogin = normalizeServerLogin(serverLogin) || DEFAULT_SHINE_SERVER_LOGIN;
  const parsed = await fetchUserPda(cleanServerLogin, solanaEndpoint);
  if (!parsed.isServer) {
    throw new Error(`Логин @${cleanServerLogin} не опубликован как сервер SHiNE.`);
  }
  if (!parsed.serverAddress) {
    throw new Error(`У server PDA пользователя @${cleanServerLogin} не задан server_address.`);
  }
  return {
    serverLogin: cleanServerLogin,
    serverAddress: parsed.serverAddress,
    serverHttp: buildHttpBase(parsed.serverAddress),
    serverUrl: buildWsUrl(parsed.serverAddress),
  };
}

export async function resolveShineServerByUserLogin(login, solanaEndpoint = SOLANA_ENDPOINT_DEFAULT) {
  const cleanLogin = normalizeServerLogin(login);
  if (!cleanLogin) throw new Error('Не указан логин пользователя.');
  const parsed = await fetchUserPda(cleanLogin, solanaEndpoint);
  const serverLogin = normalizeServerLogin(parsed.accessServers?.[0] || '');
  if (!serverLogin) {
    throw new Error(`У пользователя @${cleanLogin} в PDA не найден первый сервер доступа.`);
  }
  const resolved = await resolveShineServerByServerLogin(serverLogin, solanaEndpoint);
  return {
    login: cleanLogin,
    accessServers: parsed.accessServers,
    serverLogin: resolved.serverLogin,
    serverAddress: resolved.serverAddress,
    serverHttp: resolved.serverHttp,
    serverUrl: resolved.serverUrl,
  };
}

export async function readWalletProfileByLogin(login, solanaEndpoint = SOLANA_ENDPOINT_DEFAULT) {
  const cleanLogin = normalizeServerLogin(login);
  const parsed = await fetchUserPda(cleanLogin, solanaEndpoint);
  return {
    login: cleanLogin,
    accessServers: parsed.accessServers,
    publicKeys: parsed.publicKeys,
    homeserverSessions: parsed.homeserverSessions,
  };
}

export {
  DEFAULT_SHINE_SERVER_ADDRESS,
  DEFAULT_SHINE_SERVER_LOGIN,
  SOLANA_ENDPOINT_DEFAULT,
  buildHttpBase,
  buildWsUrl,
  normalizeServerLogin,
};
