use solana_program::{
    account_info::{next_account_info, AccountInfo},
    entrypoint,
    entrypoint::ProgramResult,
    hash::hashv,
    instruction::Instruction,
    program::{get_return_data, invoke, invoke_signed},
    program_error::ProgramError,
    program_memory::sol_memcmp,
    pubkey::Pubkey,
    rent::Rent,
    system_instruction,
    system_program,
    sysvar::{instructions::{load_current_index_checked, load_instruction_at_checked}, Sysvar},
};
use std::{convert::TryFrom, str::FromStr};

pub mod settings;

solana_program::declare_id!("FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm");
entrypoint!(process_instruction);

const MAGIC: &[u8; 5] = b"SHiNE";
const FORMAT_MAJOR: u8 = 1;
const FORMAT_MINOR: u8 = 0;
const MAX_SYNC_SERVERS: usize = 32;
const MAX_SESSIONS: usize = 64;
const MAX_SESSION_NAME_LEN: usize = 64;
const MAX_AUTO_REALLOC_INCREASE: usize = 10_000;
const ZERO_HASH: [u8; 32] = [0; 32];
const BLOCK_TYPE_ROOT_KEY: u8 = 1;
const BLOCK_TYPE_DEVICE_KEY: u8 = 2;
const BLOCK_TYPE_BLOCKCHAIN_REGISTRY: u8 = 3;
const BLOCK_TYPE_SERVER_PROFILE: u8 = 30;
const BLOCK_TYPE_ACCESS_SERVERS: u8 = 40;
const BLOCK_TYPE_SESSIONS: u8 = 50;
const BLOCK_TYPE_TRUSTED_STATE: u8 = 70;
const BLOCK_VERSION_0: u8 = 0;
const BLOCKCHAIN_TYPE_MAIN_USER: u8 = 1;
const SESSIONS_MODE_MIXED: u8 = 1;
const SESSIONS_MODE_PDA_ONLY: u8 = 10;
const SESSION_TYPE_USER: u8 = 1;
const SESSION_TYPE_HOMESERVER: u8 = 100;
const LAST_BLOCK_STATE_PREFIX: &[u8] = b"SHiNE_LAST_BLOCK";

const IX_INIT_USERS_ECONOMY_CONFIG: u8 = 1;
const IX_UPDATE_USERS_ECONOMY_CONFIG: u8 = 2;
const IX_CREATE_USER_PDA: u8 = 3;
const IX_UPDATE_USER_PDA: u8 = 4;
const LOGIN_GUARD_IX_CLASSIFY_LOGIN: u8 = 1;

#[repr(u32)]
#[derive(Clone, Copy, Debug)]
enum ShineUsersError {
    InvalidInstruction = 1,
    InvalidSigner = 2,
    InvalidPdaAddress = 3,
    UserAlreadyExists = 4,
    EmptyPdaData = 5,
    InvalidRecordData = 6,
    InvalidRecordMagic = 7,
    InvalidRecordFormat = 8,
    InvalidRecordLength = 9,
    InvalidLogin = 10,
    InvalidLimitIncrement = 11,
    InvalidFeeReceiver = 12,
    InvalidLoginGuardResponse = 13,
    PremiumLogin = 14,
    TrademarkLoginRequiresReview = 15,
    InvalidVersion = 16,
    InvalidPrevHash = 17,
    ImmutableFieldChanged = 18,
    BalanceDecrease = 19,
    InvalidSignature = 20,
    RecordTooLarge = 21,
    MathOverflow = 22,
    SystemAlreadyInitialized = 23,
    MissingRequiredSignature = 24,
    InvalidSystemProgram = 25,
    InvalidAccountOwner = 26,
    InvalidAccountData = 27,
}

impl From<ShineUsersError> for ProgramError {
    fn from(value: ShineUsersError) -> Self {
        ProgramError::Custom(value as u32)
    }
}

macro_rules! require {
    ($cond:expr, $err:expr) => {
        if !($cond) {
            return Err(ProgramError::from($err));
        }
    };
}

macro_rules! require_keys_eq {
    ($left:expr, $right:expr, $err:expr) => {
        if $left != $right {
            return Err(ProgramError::from($err));
        }
    };
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub struct SessionRecord {
    pub session_type: u8,
    pub session_version: u8,
    pub session_name: String,
    pub session_pub_key: Pubkey,
}

#[derive(Clone, Debug)]
pub struct UserMutableFields {
    pub device_key: Pubkey,
    pub blockchain_public_key: Pubkey,
    pub blockchain_name: String,
    pub used_bytes: u64,
    pub last_block_number: u32,
    pub last_block_hash: [u8; 32],
    pub last_block_signature: [u8; 64],
    pub arweave_tx_id: String,
    pub is_server: bool,
    pub address_format_type: u8,
    pub address_format_version: u8,
    pub server_address: String,
    pub sync_servers: Vec<String>,
    pub access_servers: Vec<String>,
    pub sessions_mode: u8,
    pub sessions: Vec<SessionRecord>,
    pub trusted_count: u8,
}

#[derive(Clone, Debug)]
pub struct CreateUserPdaArgs {
    pub login: String,
    pub root_key: Pubkey,
    pub created_at_ms: u64,
    pub additional_limit: u64,
    pub fields: UserMutableFields,
    pub signature: [u8; 64],
}

#[derive(Clone, Debug)]
pub struct UpdateUserPdaArgs {
    pub login: String,
    pub root_key: Pubkey,
    pub created_at_ms: u64,
    pub updated_at_ms: u64,
    pub version: u32,
    pub prev_hash: [u8; 32],
    pub additional_limit: u64,
    pub fields: UserMutableFields,
    pub signature: [u8; 64],
}

#[derive(Clone, Debug)]
pub struct UpdateUsersEconomyConfigArgs {
    pub registration_fee_lamports: u64,
    pub lamports_per_limit_step: u64,
    pub start_bonus_limit: u64,
}

#[derive(Clone, Debug)]
pub struct UsersEconomyConfigState {
    pub version: u8,
    pub registration_fee_lamports: u64,
    pub lamports_per_limit_step: u64,
    pub start_bonus_limit: u64,
}

#[derive(Clone, Debug)]
pub struct BlockchainRecord {
    pub blockchain_type: u8,
    pub blockchain_name: String,
    pub blockchain_public_key: Pubkey,
    pub paid_limit_bytes: u64,
    pub used_bytes: u64,
    pub last_block_number: u32,
    pub last_block_hash: [u8; 32],
    pub last_block_signature: [u8; 64],
    pub arweave_tx_id: String,
}

#[derive(Clone, Debug)]
pub struct UserRecord {
    pub created_at_ms: u64,
    pub updated_at_ms: u64,
    pub record_number: u32,
    pub prev_record_hash: [u8; 32],
    pub login: String,
    pub root_key: Pubkey,
    pub device_key: Pubkey,
    pub blockchain: BlockchainRecord,
    pub is_server: bool,
    pub address_format_type: u8,
    pub address_format_version: u8,
    pub server_address: String,
    pub sync_servers: Vec<String>,
    pub access_servers: Vec<String>,
    pub sessions_mode: u8,
    pub sessions: Vec<SessionRecord>,
    pub trusted_count: u8,
    pub signature: [u8; 64],
}

struct Reader<'a> {
    data: &'a [u8],
    cursor: usize,
}

impl<'a> Reader<'a> {
    fn new(data: &'a [u8]) -> Self { Self { data, cursor: 0 } }
    fn read_u8(&mut self) -> Result<u8, ProgramError> {
        let v = *self.data.get(self.cursor).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?;
        self.cursor += 1;
        Ok(v)
    }
    fn read_u32(&mut self) -> Result<u32, ProgramError> {
        let end = self.cursor.checked_add(4).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?;
        let s = self.data.get(self.cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?;
        self.cursor = end;
        Ok(u32::from_le_bytes([s[0], s[1], s[2], s[3]]))
    }
    fn read_u64(&mut self) -> Result<u64, ProgramError> {
        let end = self.cursor.checked_add(8).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?;
        let s = self.data.get(self.cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?;
        self.cursor = end;
        Ok(u64::from_le_bytes([s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]]))
    }
    fn read_fixed_32(&mut self) -> Result<[u8; 32], ProgramError> {
        let end = self.cursor.checked_add(32).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?;
        let s = self.data.get(self.cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?;
        self.cursor = end;
        <[u8; 32]>::try_from(s).map_err(|_| ProgramError::from(ShineUsersError::InvalidInstruction))
    }
    fn read_fixed_64(&mut self) -> Result<[u8; 64], ProgramError> {
        let end = self.cursor.checked_add(64).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?;
        let s = self.data.get(self.cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?;
        self.cursor = end;
        <[u8; 64]>::try_from(s).map_err(|_| ProgramError::from(ShineUsersError::InvalidInstruction))
    }
    fn read_pubkey(&mut self) -> Result<Pubkey, ProgramError> {
        Ok(Pubkey::new_from_array(self.read_fixed_32()?))
    }
    fn read_string_u8(&mut self) -> Result<String, ProgramError> {
        let len = self.read_u8()? as usize;
        let end = self.cursor.checked_add(len).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?;
        let s = self.data.get(self.cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidInstruction))?;
        self.cursor = end;
        std::str::from_utf8(s).map(|v| v.to_string()).map_err(|_| ProgramError::from(ShineUsersError::InvalidInstruction))
    }
    fn finish(self) -> Result<(), ProgramError> {
        require!(self.cursor == self.data.len(), ShineUsersError::InvalidInstruction);
        Ok(())
    }
}

fn process_instruction(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
    let mut r = Reader::new(instruction_data);
    let tag = r.read_u8()?;
    match tag {
        IX_INIT_USERS_ECONOMY_CONFIG => {
            r.finish()?;
            process_init_users_economy_config(program_id, accounts)
        }
        IX_UPDATE_USERS_ECONOMY_CONFIG => {
            let args = UpdateUsersEconomyConfigArgs {
                registration_fee_lamports: r.read_u64()?,
                lamports_per_limit_step: r.read_u64()?,
                start_bonus_limit: r.read_u64()?,
            };
            r.finish()?;
            process_update_users_economy_config(program_id, accounts, args)
        }
        IX_CREATE_USER_PDA => {
            let args = parse_create_args(&mut r)?;
            r.finish()?;
            process_create_user_pda(program_id, accounts, args)
        }
        IX_UPDATE_USER_PDA => {
            let args = parse_update_args(&mut r)?;
            r.finish()?;
            process_update_user_pda(program_id, accounts, args)
        }
        _ => Err(ProgramError::from(ShineUsersError::InvalidInstruction)),
    }
}

fn parse_create_args(r: &mut Reader<'_>) -> Result<CreateUserPdaArgs, ProgramError> {
    Ok(CreateUserPdaArgs {
        login: r.read_string_u8()?,
        root_key: r.read_pubkey()?,
        created_at_ms: r.read_u64()?,
        additional_limit: r.read_u64()?,
        fields: parse_fields(r)?,
        signature: r.read_fixed_64()?,
    })
}

fn parse_update_args(r: &mut Reader<'_>) -> Result<UpdateUserPdaArgs, ProgramError> {
    Ok(UpdateUserPdaArgs {
        login: r.read_string_u8()?,
        root_key: r.read_pubkey()?,
        created_at_ms: r.read_u64()?,
        updated_at_ms: r.read_u64()?,
        version: r.read_u32()?,
        prev_hash: r.read_fixed_32()?,
        additional_limit: r.read_u64()?,
        fields: parse_fields(r)?,
        signature: r.read_fixed_64()?,
    })
}

fn parse_fields(r: &mut Reader<'_>) -> Result<UserMutableFields, ProgramError> {
    let device_key = r.read_pubkey()?;
    let blockchain_public_key = r.read_pubkey()?;
    let blockchain_name = r.read_string_u8()?;
    let used_bytes = r.read_u64()?;
    let last_block_number = r.read_u32()?;
    let last_block_hash = r.read_fixed_32()?;
    let last_block_signature = r.read_fixed_64()?;
    let arweave_tx_id = r.read_string_u8()?;
    let is_server = r.read_u8()? == 1;
    let (address_format_type, address_format_version, server_address, sync_servers) = if is_server {
        let aft = r.read_u8()?;
        let afv = r.read_u8()?;
        let addr = r.read_string_u8()?;
        let cnt = r.read_u8()? as usize;
        let mut list = Vec::with_capacity(cnt);
        for _ in 0..cnt { list.push(r.read_string_u8()?); }
        (aft, afv, addr, list)
    } else {
        (0, 0, String::new(), Vec::new())
    };
    let access_count = r.read_u8()? as usize;
    let mut access_servers = Vec::with_capacity(access_count);
    for _ in 0..access_count { access_servers.push(r.read_string_u8()?); }
    let sessions_mode = r.read_u8()?;
    let sessions_count = r.read_u8()? as usize;
    let mut sessions = Vec::with_capacity(sessions_count);
    for _ in 0..sessions_count {
        sessions.push(SessionRecord {
            session_type: r.read_u8()?,
            session_version: r.read_u8()?,
            session_name: r.read_string_u8()?,
            session_pub_key: r.read_pubkey()?,
        });
    }
    let trusted_count = r.read_u8()?;
    Ok(UserMutableFields {
        device_key,
        blockchain_public_key,
        blockchain_name,
        used_bytes,
        last_block_number,
        last_block_hash,
        last_block_signature,
        arweave_tx_id,
        is_server,
        address_format_type,
        address_format_version,
        server_address,
        sync_servers,
        access_servers,
        sessions_mode,
        sessions,
        trusted_count,
    })
}

fn process_init_users_economy_config(program_id: &Pubkey, accounts: &[AccountInfo]) -> ProgramResult {
    let mut it = accounts.iter();
    let signer = next_account_info(&mut it)?;
    let users_economy_config_pda = next_account_info(&mut it)?;
    let system_program_ai = next_account_info(&mut it)?;
    require!(it.next().is_none(), ShineUsersError::InvalidInstruction);

    require!(signer.is_signer, ShineUsersError::InvalidSigner);
    require_keys_eq!(*system_program_ai.key, system_program::id(), ShineUsersError::InvalidSystemProgram);

    let (expected_pda, bump) = find_users_economy_config_pda(program_id);
    require_keys_eq!(expected_pda, *users_economy_config_pda.key, ShineUsersError::InvalidPdaAddress);
    require!(users_economy_config_pda.owner == &system_program::id(), ShineUsersError::SystemAlreadyInitialized);
    require!(users_economy_config_pda.data_is_empty(), ShineUsersError::SystemAlreadyInitialized);

    let state = UsersEconomyConfigState {
        version: 1,
        registration_fee_lamports: settings::START_REGISTRATION_FEE_LAMPORTS,
        lamports_per_limit_step: settings::START_LAMPORTS_PER_LIMIT_STEP,
        start_bonus_limit: settings::START_BONUS_LIMIT,
    };
    let data = serialize_users_economy_config(&state);
    create_pda_account(
        signer,
        users_economy_config_pda,
        system_program_ai,
        program_id,
        &[settings::USERS_ECONOMY_CONFIG_SEED, &[bump]],
        data.len(),
    )?;
    write_pda_exact(users_economy_config_pda, &data)?;
    Ok(())
}

fn process_update_users_economy_config(program_id: &Pubkey, accounts: &[AccountInfo], args: UpdateUsersEconomyConfigArgs) -> ProgramResult {
    let mut it = accounts.iter();
    let signer = next_account_info(&mut it)?;
    let users_economy_config_pda = next_account_info(&mut it)?;
    require!(it.next().is_none(), ShineUsersError::InvalidInstruction);

    require!(signer.is_signer, ShineUsersError::InvalidSigner);
    let dao_authority = Pubkey::from_str(settings::DAO_AUTHORITY).map_err(|_| ProgramError::from(ShineUsersError::InvalidSigner))?;
    require_keys_eq!(dao_authority, *signer.key, ShineUsersError::InvalidSigner);
    let (expected_pda, _) = find_users_economy_config_pda(program_id);
    require_keys_eq!(expected_pda, *users_economy_config_pda.key, ShineUsersError::InvalidPdaAddress);
    require!(users_economy_config_pda.owner == program_id, ShineUsersError::InvalidPdaAddress);
    require!(args.lamports_per_limit_step > 0, ShineUsersError::InvalidRecordData);

    let mut state = read_users_economy_config(users_economy_config_pda)?;
    state.registration_fee_lamports = args.registration_fee_lamports;
    state.lamports_per_limit_step = args.lamports_per_limit_step;
    state.start_bonus_limit = args.start_bonus_limit;
    write_pda_exact(users_economy_config_pda, &serialize_users_economy_config(&state))?;
    Ok(())
}

fn process_create_user_pda(program_id: &Pubkey, accounts: &[AccountInfo], args: CreateUserPdaArgs) -> ProgramResult {
    let mut it = accounts.iter();
    let signer = next_account_info(&mut it)?;
    let user_pda = next_account_info(&mut it)?;
    let system_program_ai = next_account_info(&mut it)?;
    let inflow_vault = next_account_info(&mut it)?;
    let instructions_sysvar = next_account_info(&mut it)?;
    let users_economy_config_pda = next_account_info(&mut it)?;
    let login_guard_program = next_account_info(&mut it)?;
    require!(it.next().is_none(), ShineUsersError::InvalidInstruction);

    require!(signer.is_signer, ShineUsersError::InvalidSigner);
    require_keys_eq!(*signer.key, args.fields.device_key, ShineUsersError::InvalidSigner);
    require_keys_eq!(*system_program_ai.key, system_program::id(), ShineUsersError::InvalidSystemProgram);

    validate_login(&args.login)?;
    validate_fields(&args.fields)?;
    validate_inflow_vault(inflow_vault)?;
    require!(args.additional_limit % settings::LIMIT_STEP == 0, ShineUsersError::InvalidLimitIncrement);
    require_keys_eq!(*login_guard_program.key, Pubkey::from_str(settings::SHINE_LOGIN_GUARD_PROGRAM_ID).map_err(|_| ProgramError::from(ShineUsersError::InvalidLoginGuardResponse))?, ShineUsersError::InvalidLoginGuardResponse);
    classify_login_or_fail(login_guard_program, &args.login)?;
    validate_users_economy_config_pda(program_id, users_economy_config_pda)?;

    let economy = read_users_economy_config(users_economy_config_pda)?;
    let login_seed = login_seed_normalized(&args.login);
    let (expected_pda, bump) = find_user_pda(program_id, &login_seed);
    require_keys_eq!(expected_pda, *user_pda.key, ShineUsersError::InvalidPdaAddress);
    require!(user_pda.owner == &system_program::id(), ShineUsersError::UserAlreadyExists);
    require!(user_pda.data_is_empty(), ShineUsersError::UserAlreadyExists);

    let start_balance = economy.start_bonus_limit.checked_add(args.additional_limit).ok_or(ProgramError::from(ShineUsersError::MathOverflow))?;
    let mut record = UserRecord {
        created_at_ms: args.created_at_ms,
        updated_at_ms: args.created_at_ms,
        record_number: 0,
        prev_record_hash: ZERO_HASH,
        login: args.login,
        root_key: args.root_key,
        device_key: args.fields.device_key,
        blockchain: BlockchainRecord {
            blockchain_type: BLOCKCHAIN_TYPE_MAIN_USER,
            blockchain_name: args.fields.blockchain_name,
            blockchain_public_key: args.fields.blockchain_public_key,
            paid_limit_bytes: start_balance,
            used_bytes: args.fields.used_bytes,
            last_block_number: args.fields.last_block_number,
            last_block_hash: args.fields.last_block_hash,
            last_block_signature: args.fields.last_block_signature,
            arweave_tx_id: args.fields.arweave_tx_id,
        },
        is_server: args.fields.is_server,
        address_format_type: args.fields.address_format_type,
        address_format_version: args.fields.address_format_version,
        server_address: args.fields.server_address,
        sync_servers: args.fields.sync_servers,
        access_servers: args.fields.access_servers,
        sessions_mode: args.fields.sessions_mode,
        sessions: args.fields.sessions,
        trusted_count: args.fields.trusted_count,
        signature: [0; 64],
    };
    validate_blockchain_limits(&record.blockchain, 0, 0, true)?;
    verify_last_block_state_signature(instructions_sysvar, &record)?;
    let unsigned = serialize_unsigned_record(&record)?;
    let unsigned_hash = hashv(&[&unsigned]);
    record.signature = verify_record_signature_hash(instructions_sysvar, &record.root_key, &args.signature, unsigned_hash.as_ref())?;

    let serialized = pad_to_fixed_size(serialize_full_record(&record)?, settings::USER_PDA_SPACE)?;
    create_pda_account(
        signer,
        user_pda,
        system_program_ai,
        program_id,
        &[settings::USER_PDA_SEED_PREFIX.as_bytes(), login_seed.as_bytes(), &[bump]],
        settings::USER_PDA_SPACE,
    )?;
    write_pda_exact(user_pda, &serialized)?;

    let total_fee = economy.registration_fee_lamports.checked_add(limit_fee_lamports(args.additional_limit, economy.lamports_per_limit_step)?).ok_or(ProgramError::from(ShineUsersError::MathOverflow))?;
    transfer_lamports(signer, inflow_vault, system_program_ai, total_fee)?;
    Ok(())
}

fn process_update_user_pda(program_id: &Pubkey, accounts: &[AccountInfo], args: UpdateUserPdaArgs) -> ProgramResult {
    let mut it = accounts.iter();
    let signer = next_account_info(&mut it)?;
    let user_pda = next_account_info(&mut it)?;
    let system_program_ai = next_account_info(&mut it)?;
    let inflow_vault = next_account_info(&mut it)?;
    let instructions_sysvar = next_account_info(&mut it)?;
    let users_economy_config_pda = next_account_info(&mut it)?;
    require!(it.next().is_none(), ShineUsersError::InvalidInstruction);

    require!(signer.is_signer, ShineUsersError::InvalidSigner);
    require_keys_eq!(*signer.key, args.fields.device_key, ShineUsersError::InvalidSigner);
    require_keys_eq!(*system_program_ai.key, system_program::id(), ShineUsersError::InvalidSystemProgram);

    validate_login(&args.login)?;
    validate_fields(&args.fields)?;
    validate_inflow_vault(inflow_vault)?;
    require!(args.additional_limit % settings::LIMIT_STEP == 0, ShineUsersError::InvalidLimitIncrement);
    validate_users_economy_config_pda(program_id, users_economy_config_pda)?;
    let economy = read_users_economy_config(users_economy_config_pda)?;

    let normalized_login = login_seed_normalized(&args.login);
    require_keys_eq!(find_user_pda(program_id, &normalized_login).0, *user_pda.key, ShineUsersError::InvalidPdaAddress);
    require!(user_pda.owner == program_id, ShineUsersError::InvalidPdaAddress);

    let old_record = deserialize_record_from_pda(&read_pda_all(user_pda)?)?;
    require!(old_record.login == args.login, ShineUsersError::ImmutableFieldChanged);
    require!(old_record.created_at_ms == args.created_at_ms, ShineUsersError::ImmutableFieldChanged);
    require_keys_eq!(old_record.root_key, args.root_key, ShineUsersError::ImmutableFieldChanged);
    require!(args.version == old_record.record_number.saturating_add(1), ShineUsersError::InvalidVersion);
    require!(hash_unsigned_record(&old_record)? == args.prev_hash, ShineUsersError::InvalidPrevHash);
    require!(args.fields.blockchain_name == old_record.blockchain.blockchain_name, ShineUsersError::ImmutableFieldChanged);
    require_keys_eq!(args.fields.blockchain_public_key, old_record.blockchain.blockchain_public_key, ShineUsersError::ImmutableFieldChanged);

    let new_balance = old_record.blockchain.paid_limit_bytes.checked_add(args.additional_limit).ok_or(ProgramError::from(ShineUsersError::MathOverflow))?;
    require!(new_balance >= old_record.blockchain.paid_limit_bytes, ShineUsersError::BalanceDecrease);

    let blockchain_state_unchanged = old_record.blockchain.used_bytes == args.fields.used_bytes
        && old_record.blockchain.last_block_number == args.fields.last_block_number
        && old_record.blockchain.last_block_hash == args.fields.last_block_hash
        && old_record.blockchain.last_block_signature == args.fields.last_block_signature
        && old_record.blockchain.arweave_tx_id == args.fields.arweave_tx_id;

    let mut new_record = build_update_record(&old_record, &args, new_balance)?;
    validate_blockchain_limits(&new_record.blockchain, old_record.blockchain.used_bytes, old_record.blockchain.last_block_number, false)?;
    if !blockchain_state_unchanged {
        verify_last_block_state_signature(instructions_sysvar, &new_record)?;
    }
    let unsigned = serialize_unsigned_record(&new_record)?;
    let unsigned_hash = hashv(&[&unsigned]);
    new_record.signature = verify_record_signature_hash(instructions_sysvar, &old_record.root_key, &args.signature, unsigned_hash.as_ref())?;

    let serialized = serialize_full_record(&new_record)?;
    ensure_pda_size_and_rent(user_pda, signer, system_program_ai, serialized.len())?;
    write_pda_prefix(user_pda, &serialized)?;

    let topup_fee = limit_fee_lamports(args.additional_limit, economy.lamports_per_limit_step)?;
    if topup_fee > 0 {
        transfer_lamports(signer, inflow_vault, system_program_ai, topup_fee)?;
    }
    Ok(())
}

fn build_update_record(old_record: &UserRecord, args: &UpdateUserPdaArgs, new_balance: u64) -> Result<UserRecord, ProgramError> {
    Ok(UserRecord {
        created_at_ms: old_record.created_at_ms,
        updated_at_ms: args.updated_at_ms,
        record_number: args.version,
        prev_record_hash: args.prev_hash,
        login: old_record.login.clone(),
        root_key: old_record.root_key,
        device_key: args.fields.device_key,
        blockchain: BlockchainRecord {
            blockchain_type: old_record.blockchain.blockchain_type,
            blockchain_name: args.fields.blockchain_name.clone(),
            blockchain_public_key: args.fields.blockchain_public_key,
            paid_limit_bytes: new_balance,
            used_bytes: args.fields.used_bytes,
            last_block_number: args.fields.last_block_number,
            last_block_hash: args.fields.last_block_hash,
            last_block_signature: args.fields.last_block_signature,
            arweave_tx_id: args.fields.arweave_tx_id.clone(),
        },
        is_server: args.fields.is_server,
        address_format_type: args.fields.address_format_type,
        address_format_version: args.fields.address_format_version,
        server_address: args.fields.server_address.clone(),
        sync_servers: args.fields.sync_servers.clone(),
        access_servers: args.fields.access_servers.clone(),
        sessions_mode: args.fields.sessions_mode,
        sessions: args.fields.sessions.clone(),
        trusted_count: args.fields.trusted_count,
        signature: [0; 64],
    })
}

fn classify_login_or_fail(login_guard_program: &AccountInfo, login: &str) -> ProgramResult {
    let login_guard_program_id = Pubkey::from_str(settings::SHINE_LOGIN_GUARD_PROGRAM_ID)
        .map_err(|_| ProgramError::from(ShineUsersError::InvalidLoginGuardResponse))?;
    require_keys_eq!(*login_guard_program.key, login_guard_program_id, ShineUsersError::InvalidLoginGuardResponse);
    let mut data = Vec::with_capacity(1 + 4 + login.len());
    data.push(LOGIN_GUARD_IX_CLASSIFY_LOGIN);
    data.extend_from_slice(&(login.len() as u32).to_le_bytes());
    data.extend_from_slice(login.as_bytes());
    let ix = Instruction { program_id: login_guard_program_id, accounts: vec![], data };
    invoke(&ix, &[login_guard_program.clone()])?;
    let (program_id, raw) = get_return_data().ok_or(ProgramError::from(ShineUsersError::InvalidLoginGuardResponse))?;
    require_keys_eq!(program_id, login_guard_program_id, ShineUsersError::InvalidLoginGuardResponse);
    require!(raw.len() == 4, ShineUsersError::InvalidLoginGuardResponse);
    let class = u32::from_le_bytes([raw[0], raw[1], raw[2], raw[3]]);
    match class {
        0 => Ok(()),
        1 => Err(ProgramError::from(ShineUsersError::PremiumLogin)),
        2 => Err(ProgramError::from(ShineUsersError::TrademarkLoginRequiresReview)),
        _ => Err(ProgramError::from(ShineUsersError::InvalidLoginGuardResponse)),
    }
}

fn serialize_users_economy_config(state: &UsersEconomyConfigState) -> Vec<u8> {
    let mut out = Vec::with_capacity(1 + 8 + 8 + 8);
    out.push(state.version);
    out.extend_from_slice(&state.registration_fee_lamports.to_le_bytes());
    out.extend_from_slice(&state.lamports_per_limit_step.to_le_bytes());
    out.extend_from_slice(&state.start_bonus_limit.to_le_bytes());
    out
}

fn validate_users_economy_config_pda(program_id: &Pubkey, pda: &AccountInfo) -> ProgramResult {
    let (expected_pda, _) = find_users_economy_config_pda(program_id);
    require_keys_eq!(expected_pda, *pda.key, ShineUsersError::InvalidPdaAddress);
    require!(pda.owner == program_id, ShineUsersError::InvalidPdaAddress);
    Ok(())
}

fn read_users_economy_config(pda: &AccountInfo) -> Result<UsersEconomyConfigState, ProgramError> {
    let raw = read_pda_all(pda)?;
    require!(!raw.is_empty(), ShineUsersError::EmptyPdaData);
    require!(raw.len() >= 25, ShineUsersError::InvalidAccountData);
    Ok(UsersEconomyConfigState {
        version: raw[0],
        registration_fee_lamports: u64::from_le_bytes(raw[1..9].try_into().unwrap()),
        lamports_per_limit_step: u64::from_le_bytes(raw[9..17].try_into().unwrap()),
        start_bonus_limit: u64::from_le_bytes(raw[17..25].try_into().unwrap()),
    })
}

fn deserialize_record_from_pda(raw: &[u8]) -> Result<UserRecord, ProgramError> {
    require!(raw.len() >= 9, ShineUsersError::InvalidRecordData);
    require!(sol_memcmp(&raw[0..5], MAGIC, 5) == 0, ShineUsersError::InvalidRecordMagic);
    require!(raw[5] == FORMAT_MAJOR && raw[6] == FORMAT_MINOR, ShineUsersError::InvalidRecordFormat);
    let record_len = u16::from_le_bytes([raw[7], raw[8]]) as usize;
    require!(record_len >= 9 + 64, ShineUsersError::InvalidRecordLength);
    require!(record_len <= raw.len(), ShineUsersError::InvalidRecordLength);
    let useful = &raw[..record_len];
    let mut cursor = 9usize;

    let created_at_ms = read_u64_from(useful, &mut cursor)?;
    let updated_at_ms = read_u64_from(useful, &mut cursor)?;
    let record_number = read_u32_from(useful, &mut cursor)?;
    let prev_record_hash = read_fixed_32_from(useful, &mut cursor)?;
    let login = read_len_prefixed_string_from(useful, &mut cursor)?;

    let blocks_count = read_u8_from(useful, &mut cursor)? as usize;
    let mut root_key = None;
    let mut device_key = None;
    let mut blockchain = None;
    let mut is_server = false;
    let mut address_format_type = 0u8;
    let mut address_format_version = 0u8;
    let mut server_address = String::new();
    let mut sync_servers = Vec::new();
    let mut access_servers = Vec::new();
    let mut sessions_mode = SESSIONS_MODE_MIXED;
    let mut sessions = Vec::new();
    let mut trusted_count = 0u8;

    for _ in 0..blocks_count {
        let block_type = read_u8_from(useful, &mut cursor)?;
        let block_version = read_u8_from(useful, &mut cursor)?;
        require!(block_version == BLOCK_VERSION_0, ShineUsersError::InvalidRecordFormat);
        match block_type {
            BLOCK_TYPE_ROOT_KEY => {
                require!(root_key.is_none(), ShineUsersError::InvalidRecordData);
                root_key = Some(Pubkey::new_from_array(read_fixed_32_from(useful, &mut cursor)?));
            }
            BLOCK_TYPE_DEVICE_KEY => {
                require!(device_key.is_none(), ShineUsersError::InvalidRecordData);
                device_key = Some(Pubkey::new_from_array(read_fixed_32_from(useful, &mut cursor)?));
            }
            BLOCK_TYPE_BLOCKCHAIN_REGISTRY => {
                require!(blockchain.is_none(), ShineUsersError::InvalidRecordData);
                let count = read_u8_from(useful, &mut cursor)?;
                require!(count == 1, ShineUsersError::InvalidRecordData);
                blockchain = Some(read_blockchain_record(useful, &mut cursor)?);
            }
            BLOCK_TYPE_SERVER_PROFILE => {
                require!(!is_server, ShineUsersError::InvalidRecordData);
                is_server = read_u8_from(useful, &mut cursor)? == 1;
                require!(is_server, ShineUsersError::InvalidRecordData);
                address_format_type = read_u8_from(useful, &mut cursor)?;
                address_format_version = read_u8_from(useful, &mut cursor)?;
                server_address = read_len_prefixed_string_from(useful, &mut cursor)?;
                let sync_count = read_u8_from(useful, &mut cursor)? as usize;
                require!(sync_count <= MAX_SYNC_SERVERS, ShineUsersError::InvalidRecordData);
                for _ in 0..sync_count { sync_servers.push(read_len_prefixed_string_from(useful, &mut cursor)?); }
            }
            BLOCK_TYPE_ACCESS_SERVERS => {
                require!(access_servers.is_empty(), ShineUsersError::InvalidRecordData);
                let access_count = read_u8_from(useful, &mut cursor)? as usize;
                for _ in 0..access_count { access_servers.push(read_len_prefixed_string_from(useful, &mut cursor)?); }
            }
            BLOCK_TYPE_SESSIONS => {
                require!(sessions.is_empty(), ShineUsersError::InvalidRecordData);
                sessions_mode = read_u8_from(useful, &mut cursor)?;
                let sessions_count = read_u8_from(useful, &mut cursor)? as usize;
                require!(sessions_count <= MAX_SESSIONS, ShineUsersError::InvalidRecordData);
                for _ in 0..sessions_count { sessions.push(read_session_record(useful, &mut cursor)?); }
            }
            BLOCK_TYPE_TRUSTED_STATE => {
                trusted_count = read_u8_from(useful, &mut cursor)?;
            }
            _ => return Err(ProgramError::from(ShineUsersError::InvalidRecordFormat)),
        }
    }
    validate_sessions_fields(sessions_mode, &sessions)?;
    let signature = read_fixed_64_from(useful, &mut cursor)?;
    require!(cursor == useful.len(), ShineUsersError::InvalidRecordLength);

    Ok(UserRecord {
        created_at_ms,
        updated_at_ms,
        record_number,
        prev_record_hash,
        login,
        root_key: root_key.ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?,
        device_key: device_key.ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?,
        blockchain: blockchain.ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?,
        is_server,
        address_format_type,
        address_format_version,
        server_address,
        sync_servers,
        access_servers,
        sessions_mode,
        sessions,
        trusted_count,
        signature,
    })
}

fn read_blockchain_record(data: &[u8], cursor: &mut usize) -> Result<BlockchainRecord, ProgramError> {
    let blockchain_type = read_u8_from(data, cursor)?;
    require!(blockchain_type == BLOCKCHAIN_TYPE_MAIN_USER, ShineUsersError::InvalidRecordData);
    let blockchain_name = read_len_prefixed_string_from(data, cursor)?;
    let blockchain_public_key = Pubkey::new_from_array(read_fixed_32_from(data, cursor)?);
    let paid_limit_bytes = read_u64_from(data, cursor)?;
    let used_bytes = read_u64_from(data, cursor)?;
    let last_block_number = read_u32_from(data, cursor)?;
    let last_block_hash = read_fixed_32_from(data, cursor)?;
    let last_block_signature = read_fixed_64_from(data, cursor)?;
    let arweave_present = read_u8_from(data, cursor)?;
    let arweave_tx_id = match arweave_present { 0 => String::new(), 1 => read_len_prefixed_string_from(data, cursor)?, _ => return Err(ProgramError::from(ShineUsersError::InvalidRecordData)) };
    Ok(BlockchainRecord {
        blockchain_type,
        blockchain_name,
        blockchain_public_key,
        paid_limit_bytes,
        used_bytes,
        last_block_number,
        last_block_hash,
        last_block_signature,
        arweave_tx_id,
    })
}

fn read_session_record(data: &[u8], cursor: &mut usize) -> Result<SessionRecord, ProgramError> {
    Ok(SessionRecord {
        session_type: read_u8_from(data, cursor)?,
        session_version: read_u8_from(data, cursor)?,
        session_name: read_len_prefixed_string_from(data, cursor)?,
        session_pub_key: Pubkey::new_from_array(read_fixed_32_from(data, cursor)?),
    })
}

fn serialize_unsigned_record(record: &UserRecord) -> Result<Vec<u8>, ProgramError> {
    let login_bytes = record.login.as_bytes();
    require!(login_bytes.len() <= u8::MAX as usize, ShineUsersError::InvalidLogin);
    let mut out = Vec::new();
    out.extend_from_slice(MAGIC);
    out.push(FORMAT_MAJOR);
    out.push(FORMAT_MINOR);
    out.extend_from_slice(&0u16.to_le_bytes());
    out.extend_from_slice(&record.created_at_ms.to_le_bytes());
    out.extend_from_slice(&record.updated_at_ms.to_le_bytes());
    out.extend_from_slice(&record.record_number.to_le_bytes());
    out.extend_from_slice(&record.prev_record_hash);
    out.push(login_bytes.len() as u8);
    out.extend_from_slice(login_bytes);

    let blocks_count = if record.is_server { 7 } else { 6 };
    out.push(blocks_count);
    write_root_key_block(&mut out, record);
    write_device_key_block(&mut out, record);
    write_blockchain_registry_block(&mut out, &record.blockchain)?;
    if record.is_server { write_server_profile_block(&mut out, record)?; }
    write_access_servers_block(&mut out, record)?;
    write_sessions_block(&mut out, record)?;
    write_trusted_state_block(&mut out, record);

    let record_len = out.len().checked_add(64).ok_or(ProgramError::from(ShineUsersError::MathOverflow))?;
    require!(record_len <= u16::MAX as usize, ShineUsersError::RecordTooLarge);
    let len_bytes = (record_len as u16).to_le_bytes();
    out[7] = len_bytes[0];
    out[8] = len_bytes[1];
    Ok(out)
}

fn serialize_full_record(record: &UserRecord) -> Result<Vec<u8>, ProgramError> {
    let mut out = serialize_unsigned_record(record)?;
    out.extend_from_slice(&record.signature);
    Ok(out)
}

fn hash_unsigned_record(record: &UserRecord) -> Result<[u8; 32], ProgramError> {
    let unsigned = serialize_unsigned_record(record)?;
    let digest = hashv(&[&unsigned]);
    let mut out = [0u8; 32];
    out.copy_from_slice(digest.as_ref());
    Ok(out)
}

fn write_root_key_block(out: &mut Vec<u8>, record: &UserRecord) { out.push(BLOCK_TYPE_ROOT_KEY); out.push(BLOCK_VERSION_0); out.extend_from_slice(record.root_key.as_ref()); }
fn write_device_key_block(out: &mut Vec<u8>, record: &UserRecord) { out.push(BLOCK_TYPE_DEVICE_KEY); out.push(BLOCK_VERSION_0); out.extend_from_slice(record.device_key.as_ref()); }
fn write_blockchain_registry_block(out: &mut Vec<u8>, blockchain: &BlockchainRecord) -> Result<(), ProgramError> { out.push(BLOCK_TYPE_BLOCKCHAIN_REGISTRY); out.push(BLOCK_VERSION_0); out.push(1); write_blockchain_record(out, blockchain) }
fn write_blockchain_record(out: &mut Vec<u8>, blockchain: &BlockchainRecord) -> Result<(), ProgramError> {
    out.push(blockchain.blockchain_type);
    write_len_prefixed_string(out, &blockchain.blockchain_name)?;
    out.extend_from_slice(blockchain.blockchain_public_key.as_ref());
    out.extend_from_slice(&blockchain.paid_limit_bytes.to_le_bytes());
    out.extend_from_slice(&blockchain.used_bytes.to_le_bytes());
    out.extend_from_slice(&blockchain.last_block_number.to_le_bytes());
    out.extend_from_slice(&blockchain.last_block_hash);
    out.extend_from_slice(&blockchain.last_block_signature);
    if blockchain.arweave_tx_id.is_empty() { out.push(0); } else { out.push(1); write_len_prefixed_string(out, &blockchain.arweave_tx_id)?; }
    Ok(())
}
fn write_server_profile_block(out: &mut Vec<u8>, record: &UserRecord) -> Result<(), ProgramError> {
    out.push(BLOCK_TYPE_SERVER_PROFILE); out.push(BLOCK_VERSION_0); out.push(1); out.push(record.address_format_type); out.push(record.address_format_version); write_len_prefixed_string(out, &record.server_address)?; require!(record.sync_servers.len() <= MAX_SYNC_SERVERS, ShineUsersError::InvalidRecordData); out.push(record.sync_servers.len() as u8); for login in &record.sync_servers { write_len_prefixed_string(out, login)?; } Ok(())
}
fn write_access_servers_block(out: &mut Vec<u8>, record: &UserRecord) -> Result<(), ProgramError> {
    out.push(BLOCK_TYPE_ACCESS_SERVERS); out.push(BLOCK_VERSION_0); require!(record.access_servers.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData); out.push(record.access_servers.len() as u8); for login in &record.access_servers { write_len_prefixed_string(out, login)?; } Ok(())
}
fn write_sessions_block(out: &mut Vec<u8>, record: &UserRecord) -> Result<(), ProgramError> {
    out.push(BLOCK_TYPE_SESSIONS); out.push(BLOCK_VERSION_0); out.push(record.sessions_mode); require!(record.sessions.len() <= MAX_SESSIONS, ShineUsersError::InvalidRecordData); out.push(record.sessions.len() as u8); for session in &record.sessions { out.push(session.session_type); out.push(session.session_version); write_len_prefixed_string(out, &session.session_name)?; out.extend_from_slice(session.session_pub_key.as_ref()); } Ok(())
}
fn write_trusted_state_block(out: &mut Vec<u8>, record: &UserRecord) { out.push(BLOCK_TYPE_TRUSTED_STATE); out.push(BLOCK_VERSION_0); out.push(record.trusted_count); }
fn write_len_prefixed_string(out: &mut Vec<u8>, value: &str) -> Result<(), ProgramError> { let bytes = value.as_bytes(); require!(bytes.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData); out.push(bytes.len() as u8); out.extend_from_slice(bytes); Ok(()) }

fn verify_record_signature_hash(instructions_sysvar: &AccountInfo, root_key: &Pubkey, signature: &[u8; 64], message_hash: &[u8]) -> Result<[u8; 64], ProgramError> {
    verify_ed25519_signature_instruction(instructions_sysvar, -2, root_key, signature, message_hash)?;
    Ok(*signature)
}

fn verify_last_block_state_signature(instructions_sysvar: &AccountInfo, record: &UserRecord) -> ProgramResult {
    let message = serialize_last_block_state(record)?;
    let msg_hash = hashv(&[&message]);
    verify_ed25519_signature_instruction(instructions_sysvar, -1, &record.blockchain.blockchain_public_key, &record.blockchain.last_block_signature, msg_hash.as_ref())
}

fn serialize_last_block_state(record: &UserRecord) -> Result<Vec<u8>, ProgramError> {
    let mut out = Vec::new();
    out.extend_from_slice(LAST_BLOCK_STATE_PREFIX);
    write_len_prefixed_string(&mut out, &record.login)?;
    write_len_prefixed_string(&mut out, &record.blockchain.blockchain_name)?;
    out.extend_from_slice(&record.blockchain.last_block_number.to_le_bytes());
    out.extend_from_slice(&record.blockchain.last_block_hash);
    out.extend_from_slice(&record.blockchain.used_bytes.to_le_bytes());
    Ok(out)
}

struct ParsedEd25519Ref<'a> {
    pubkey: Pubkey,
    signature: [u8; 64],
    message: &'a [u8],
}

fn verify_ed25519_signature_instruction(instructions_sysvar: &AccountInfo, index_relative_to_current: i64, expected_pubkey: &Pubkey, expected_signature: &[u8; 64], expected_message: &[u8]) -> ProgramResult {
    require_keys_eq!(*instructions_sysvar.key, solana_program::sysvar::instructions::id(), ShineUsersError::InvalidSignature);
    let current_index = load_current_index_checked(instructions_sysvar).map_err(|_| ProgramError::from(ShineUsersError::InvalidSignature))? as i64;
    let target_index = current_index.checked_add(index_relative_to_current).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?;
    require!(target_index >= 0, ShineUsersError::InvalidSignature);
    let ed_ix = load_instruction_at_checked(target_index as usize, instructions_sysvar).map_err(|_| ProgramError::from(ShineUsersError::InvalidSignature))?;
    require_keys_eq!(ed_ix.program_id, solana_program::ed25519_program::id(), ShineUsersError::InvalidSignature);
    let parsed = parse_ed25519_ix(ed_ix.data.as_slice())?;
    require!(parsed.pubkey == *expected_pubkey, ShineUsersError::InvalidSignature);
    require!(parsed.signature == *expected_signature, ShineUsersError::InvalidSignature);
    require!(parsed.message == expected_message, ShineUsersError::InvalidSignature);
    Ok(())
}

fn parse_ed25519_ix<'a>(data: &'a [u8]) -> Result<ParsedEd25519Ref<'a>, ProgramError> {
    require!(data.len() >= 16, ShineUsersError::InvalidSignature);
    require!(data[0] == 1, ShineUsersError::InvalidSignature);
    let signature_offset = le_u16(data, 2)? as usize;
    let signature_ix_index = le_u16(data, 4)?;
    let pubkey_offset = le_u16(data, 6)? as usize;
    let pubkey_ix_index = le_u16(data, 8)?;
    let message_offset = le_u16(data, 10)? as usize;
    let message_size = le_u16(data, 12)? as usize;
    let message_ix_index = le_u16(data, 14)?;
    require!(signature_ix_index == u16::MAX, ShineUsersError::InvalidSignature);
    require!(pubkey_ix_index == u16::MAX, ShineUsersError::InvalidSignature);
    require!(message_ix_index == u16::MAX, ShineUsersError::InvalidSignature);

    let signature_end = signature_offset.checked_add(64).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?;
    let pubkey_end = pubkey_offset.checked_add(32).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?;
    let message_end = message_offset.checked_add(message_size).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?;
    let signature_slice = data.get(signature_offset..signature_end).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?;
    let pubkey_slice = data.get(pubkey_offset..pubkey_end).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?;
    let message = data.get(message_offset..message_end).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?;
    let mut signature = [0u8; 64]; signature.copy_from_slice(signature_slice);
    let pubkey = Pubkey::new_from_array(<[u8; 32]>::try_from(pubkey_slice).map_err(|_| ProgramError::from(ShineUsersError::InvalidSignature))?);
    Ok(ParsedEd25519Ref { pubkey, signature, message })
}

fn le_u16(data: &[u8], offset: usize) -> Result<u16, ProgramError> {
    let end = offset.checked_add(2).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?;
    let s = data.get(offset..end).ok_or(ProgramError::from(ShineUsersError::InvalidSignature))?;
    Ok(u16::from_le_bytes([s[0], s[1]]))
}

fn validate_login(login: &str) -> ProgramResult {
    require!(!login.is_empty(), ShineUsersError::InvalidLogin);
    require!(login.len() <= 20, ShineUsersError::InvalidLogin);
    for ch in login.chars() { if !(ch.is_ascii_alphabetic() || ch.is_ascii_digit() || ch == '_') { return Err(ProgramError::from(ShineUsersError::InvalidLogin)); } }
    Ok(())
}
fn login_seed_normalized(login: &str) -> String { login.to_ascii_lowercase() }

fn validate_fields(fields: &UserMutableFields) -> ProgramResult {
    require!(!fields.blockchain_name.is_empty(), ShineUsersError::InvalidRecordData);
    require!(fields.blockchain_name.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData);
    require!(fields.arweave_tx_id.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData);
    if fields.is_server {
        require!(!fields.server_address.is_empty(), ShineUsersError::InvalidRecordData);
        require!(fields.server_address.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData);
        require!(fields.sync_servers.len() <= MAX_SYNC_SERVERS, ShineUsersError::InvalidRecordData);
        for login in &fields.sync_servers { require!(!login.is_empty(), ShineUsersError::InvalidRecordData); require!(login.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData); }
    } else {
        require!(fields.server_address.is_empty(), ShineUsersError::InvalidRecordData);
        require!(fields.sync_servers.is_empty(), ShineUsersError::InvalidRecordData);
    }
    require!(fields.access_servers.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData);
    for login in &fields.access_servers { require!(!login.is_empty(), ShineUsersError::InvalidRecordData); require!(login.len() <= u8::MAX as usize, ShineUsersError::InvalidRecordData); }
    validate_sessions_fields(fields.sessions_mode, &fields.sessions)
}

fn validate_sessions_fields(mode: u8, sessions: &[SessionRecord]) -> ProgramResult {
    require!(mode == SESSIONS_MODE_MIXED || mode == SESSIONS_MODE_PDA_ONLY, ShineUsersError::InvalidRecordData);
    require!(sessions.len() <= MAX_SESSIONS, ShineUsersError::InvalidRecordData);
    for i in 0..sessions.len() {
        validate_session_record(&sessions[i])?;
        for j in (i + 1)..sessions.len() {
            require!(sessions[i].session_name != sessions[j].session_name, ShineUsersError::InvalidRecordData);
            require!(sessions[i].session_pub_key != sessions[j].session_pub_key, ShineUsersError::InvalidRecordData);
        }
    }
    Ok(())
}

fn validate_session_record(session: &SessionRecord) -> ProgramResult {
    require!(session.session_type == SESSION_TYPE_USER || session.session_type == SESSION_TYPE_HOMESERVER, ShineUsersError::InvalidRecordData);
    require!(session.session_version == 1, ShineUsersError::InvalidRecordData);
    let bytes = session.session_name.as_bytes();
    require!(!bytes.is_empty(), ShineUsersError::InvalidRecordData);
    require!(bytes.len() <= MAX_SESSION_NAME_LEN, ShineUsersError::InvalidRecordData);
    for &b in bytes { require!(b.is_ascii_alphanumeric() || b == b'_', ShineUsersError::InvalidRecordData); }
    Ok(())
}

fn validate_blockchain_limits(blockchain: &BlockchainRecord, old_used_bytes: u64, old_last_block_number: u32, is_create: bool) -> ProgramResult {
    require!(blockchain.blockchain_type == BLOCKCHAIN_TYPE_MAIN_USER, ShineUsersError::InvalidRecordData);
    require!(blockchain.used_bytes <= blockchain.paid_limit_bytes, ShineUsersError::InvalidRecordData);
    if !is_create {
        require!(blockchain.used_bytes >= old_used_bytes && blockchain.last_block_number >= old_last_block_number, ShineUsersError::InvalidRecordData);
    }
    Ok(())
}

fn validate_inflow_vault(inflow_vault: &AccountInfo) -> ProgramResult {
    let payments_program_id = Pubkey::from_str(settings::SHINE_PAYMENTS_PROGRAM_ID).map_err(|_| ProgramError::from(ShineUsersError::InvalidFeeReceiver))?;
    let (expected, _) = Pubkey::find_program_address(&[settings::SHINE_PAYMENTS_INFLOW_VAULT_SEED], &payments_program_id);
    require_keys_eq!(expected, *inflow_vault.key, ShineUsersError::InvalidFeeReceiver);
    Ok(())
}

fn transfer_lamports<'a>(payer: &AccountInfo<'a>, recipient: &AccountInfo<'a>, system_program_ai: &AccountInfo<'a>, lamports: u64) -> ProgramResult {
    if lamports == 0 { return Ok(()); }
    let ix = system_instruction::transfer(payer.key, recipient.key, lamports);
    invoke(&ix, &[payer.clone(), recipient.clone(), system_program_ai.clone()])
}

// Создание PDA, устойчивое к «минированию» детерминированного адреса.
// Адрес будущей записи логина выводится из самого логина, поэтому злоумышленник
// может заранее вычислить адрес и перевести на него немного лампортов обычным
// system-переводом. Тогда обычный system_instruction::create_account упал бы
// («account already in use») и заблокировал бы регистрацию этого логина навсегда.
// Чтобы это исключить, при уже существующих на адресе лампортах создаём аккаунт
// «поверх предзаполненного»: доводим ренту переводом, затем allocate + assign
// под подписью PDA. Подсев чужих лампортов больше ничего не ломает.
fn create_pda_account<'a>(payer: &AccountInfo<'a>, pda: &AccountInfo<'a>, system_program_ai: &AccountInfo<'a>, owner: &Pubkey, seeds: &[&[u8]], space: usize) -> ProgramResult {
    let rent = Rent::get()?;
    let required_lamports = rent.minimum_balance(space);
    let current_lamports = pda.lamports();

    if current_lamports == 0 {
        // Быстрый путь: адрес пуст — обычное создание аккаунта одной инструкцией.
        let ix = system_instruction::create_account(payer.key, pda.key, required_lamports, space as u64, owner);
        return invoke_signed(&ix, &[payer.clone(), pda.clone(), system_program_ai.clone()], &[seeds]);
    }

    // На адресе уже лежат лампорты (вероятно, «подсев» атакующим). Доводим баланс
    // до рент-экземпта, выделяем место и назначаем владельцем нашу программу.
    let top_up = required_lamports.saturating_sub(current_lamports);
    transfer_lamports(payer, pda, system_program_ai, top_up)?;

    let allocate_ix = system_instruction::allocate(pda.key, space as u64);
    invoke_signed(&allocate_ix, &[pda.clone(), system_program_ai.clone()], &[seeds])?;

    let assign_ix = system_instruction::assign(pda.key, owner);
    invoke_signed(&assign_ix, &[pda.clone(), system_program_ai.clone()], &[seeds])
}

fn ensure_pda_size_and_rent<'a>(pda: &AccountInfo<'a>, payer: &AccountInfo<'a>, system_program_ai: &AccountInfo<'a>, required_len: usize) -> ProgramResult {
    let current_len = pda.data_len();
    if required_len <= current_len { return Ok(()); }
    let increase = required_len.checked_sub(current_len).ok_or(ProgramError::from(ShineUsersError::MathOverflow))?;
    require!(increase <= MAX_AUTO_REALLOC_INCREASE, ShineUsersError::RecordTooLarge);
    let rent = Rent::get()?;
    let required_lamports = rent.minimum_balance(required_len);
    let current_lamports = pda.lamports();
    let top_up = required_lamports.saturating_sub(current_lamports);
    if top_up > 0 { transfer_lamports(payer, pda, system_program_ai, top_up)?; }
    pda.realloc(required_len, false)
}

fn find_user_pda(program_id: &Pubkey, login: &str) -> (Pubkey, u8) { Pubkey::find_program_address(&[settings::USER_PDA_SEED_PREFIX.as_bytes(), login.as_bytes()], program_id) }
fn find_users_economy_config_pda(program_id: &Pubkey) -> (Pubkey, u8) { Pubkey::find_program_address(&[settings::USERS_ECONOMY_CONFIG_SEED], program_id) }
fn limit_fee_lamports(limit_delta: u64, lamports_per_limit_step: u64) -> Result<u64, ProgramError> { (limit_delta / settings::LIMIT_STEP).checked_mul(lamports_per_limit_step).ok_or(ProgramError::from(ShineUsersError::MathOverflow)) }
fn pad_to_fixed_size(mut bytes: Vec<u8>, target_size: usize) -> Result<Vec<u8>, ProgramError> { require!(bytes.len() <= target_size, ShineUsersError::RecordTooLarge); bytes.resize(target_size, 0); Ok(bytes) }

fn read_pda_all(pda: &AccountInfo) -> Result<Vec<u8>, ProgramError> { Ok(pda.try_borrow_data().map_err(|_| ProgramError::from(ShineUsersError::InvalidAccountData))?.to_vec()) }
fn write_pda_exact(pda: &AccountInfo, data: &[u8]) -> ProgramResult { let mut dst = pda.try_borrow_mut_data().map_err(|_| ProgramError::from(ShineUsersError::InvalidAccountData))?; require!(data.len() <= dst.len(), ShineUsersError::RecordTooLarge); dst[..data.len()].copy_from_slice(data); for b in &mut dst[data.len()..] { *b = 0; } Ok(()) }
fn write_pda_prefix(pda: &AccountInfo, data: &[u8]) -> ProgramResult { let mut dst = pda.try_borrow_mut_data().map_err(|_| ProgramError::from(ShineUsersError::InvalidAccountData))?; require!(data.len() <= dst.len(), ShineUsersError::RecordTooLarge); dst[..data.len()].copy_from_slice(data); Ok(()) }

fn read_u8_from(data: &[u8], cursor: &mut usize) -> Result<u8, ProgramError> { let v = *data.get(*cursor).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; *cursor += 1; Ok(v) }
fn read_u32_from(data: &[u8], cursor: &mut usize) -> Result<u32, ProgramError> { let end = cursor.checked_add(4).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; let s = data.get(*cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; *cursor = end; Ok(u32::from_le_bytes([s[0], s[1], s[2], s[3]])) }
fn read_u64_from(data: &[u8], cursor: &mut usize) -> Result<u64, ProgramError> { let end = cursor.checked_add(8).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; let s = data.get(*cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; *cursor = end; Ok(u64::from_le_bytes([s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7]])) }
fn read_fixed_32_from(data: &[u8], cursor: &mut usize) -> Result<[u8; 32], ProgramError> { let end = cursor.checked_add(32).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; let s = data.get(*cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; *cursor = end; <[u8; 32]>::try_from(s).map_err(|_| ProgramError::from(ShineUsersError::InvalidRecordData)) }
fn read_fixed_64_from(data: &[u8], cursor: &mut usize) -> Result<[u8; 64], ProgramError> { let end = cursor.checked_add(64).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; let s = data.get(*cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; *cursor = end; <[u8; 64]>::try_from(s).map_err(|_| ProgramError::from(ShineUsersError::InvalidRecordData)) }
fn read_len_prefixed_string_from(data: &[u8], cursor: &mut usize) -> Result<String, ProgramError> { let len = read_u8_from(data, cursor)? as usize; let end = cursor.checked_add(len).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; let s = data.get(*cursor..end).ok_or(ProgramError::from(ShineUsersError::InvalidRecordData))?; *cursor = end; std::str::from_utf8(s).map(|v| v.to_string()).map_err(|_| ProgramError::from(ShineUsersError::InvalidRecordData)) }
