<?php
/*
 * AIWK Wallet MVP Library
 * 다른 코드에서 wallet 기능을 쓰려면 이 파일을 include한다.
 * require_once __DIR__ . '/wallet/wallet_lib.php';
 */
require_once dirname(__FILE__) . '/wallet_config.php';

function wallet_uuid_v4()
{
    if (function_exists('random_bytes')) {
        $data = random_bytes(16);
    } elseif (function_exists('openssl_random_pseudo_bytes')) {
        $data = openssl_random_pseudo_bytes(16);
    } else {
        $data = '';
        for ($i = 0; $i < 16; $i++) $data .= chr(mt_rand(0, 255));
    }
    $data[6] = chr((ord($data[6]) & 0x0f) | 0x40);
    $data[8] = chr((ord($data[8]) & 0x3f) | 0x80);
    return vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($data), 4));
}

function wallet_begin()
{
    sql_query('START TRANSACTION', true, wallet_db());
}

function wallet_commit()
{
    sql_query('COMMIT', true, wallet_db());
}

function wallet_rollback()
{
    sql_query('ROLLBACK', false, wallet_db());
}

function wallet_require_user_id($user_id)
{
    $user_id = (int)$user_id;
    if ($user_id <= 0) throw new Exception(wallet_t('invalid_user'));
    return $user_id;
}

function wallet_require_amount($amount)
{
    $amount = (int)$amount;
    if ($amount <= 0) throw new Exception(wallet_t('invalid_amount'));
    return $amount;
}

/* 계정과 PAID/BONUS/LOCKED 잔액 row를 보장한다. */
function wallet_ensure_account($user_id)
{
    $user_id = wallet_require_user_id($user_id);
    $account = wallet_table('account');
    $balance = wallet_table('balance');

    sql_query("INSERT INTO `{$account}` (`user_id`, `status`) VALUES ({$user_id}, 'ACTIVE') ON DUPLICATE KEY UPDATE `user_id`=`user_id`", true, wallet_db());

    $buckets = array(AIWK_WALLET_BUCKET_PAID, AIWK_WALLET_BUCKET_BONUS, AIWK_WALLET_BUCKET_LOCKED);
    foreach ($buckets as $bucket) {
        $bucket_sql = wallet_sql_escape($bucket);
        $cur_sql = wallet_sql_escape(AIWK_WALLET_CURRENCY);
        sql_query("INSERT INTO `{$balance}` (`user_id`, `currency`, `bucket`, `balance`) VALUES ({$user_id}, '{$cur_sql}', '{$bucket_sql}', 0) ON DUPLICATE KEY UPDATE `user_id`=`user_id`", true, wallet_db());
    }
    return true;
}

function wallet_lock_balance_rows($user_id)
{
    $user_id = wallet_require_user_id($user_id);
    wallet_ensure_account($user_id);
    $balance = wallet_table('balance');
    $cur_sql = wallet_sql_escape(AIWK_WALLET_CURRENCY);
    $rs = sql_query("SELECT * FROM `{$balance}` WHERE `user_id`={$user_id} AND `currency`='{$cur_sql}' FOR UPDATE", true, wallet_db());
    $rows = array();
    while ($r = sql_fetch_array($rs)) {
        $rows[$r['bucket']] = (int)$r['balance'];
    }
    return $rows;
}

function wallet_get_balances($user_id)
{
    $user_id = wallet_require_user_id($user_id);
    wallet_ensure_account($user_id);
    $balance = wallet_table('balance');
    $cur_sql = wallet_sql_escape(AIWK_WALLET_CURRENCY);
    $rs = sql_query("SELECT `bucket`, `balance` FROM `{$balance}` WHERE `user_id`={$user_id} AND `currency`='{$cur_sql}'", true, wallet_db());
    $out = array('PAID' => 0, 'BONUS' => 0, 'LOCKED' => 0, 'AVAILABLE' => 0, 'TOTAL' => 0);
    while ($r = sql_fetch_array($rs)) {
        $out[$r['bucket']] = (int)$r['balance'];
    }
    $out['AVAILABLE'] = $out['PAID'] + $out['BONUS'];
    $out['TOTAL'] = $out['AVAILABLE'] + $out['LOCKED'];
    return $out;
}

function wallet_get_available_balance($user_id)
{
    $b = wallet_get_balances($user_id);
    return (int)$b['AVAILABLE'];
}

function wallet_add_ledger($tx_id, $tx_type, $from_user_id, $to_user_id, $amount, $fee_amount, $from_bucket, $to_bucket, $memo, $ref_type, $ref_id)
{
    $ledger = wallet_table('ledger');
    $tx_id = wallet_sql_escape($tx_id);
    $tx_type = wallet_sql_escape($tx_type);
    $cur = wallet_sql_escape(AIWK_WALLET_CURRENCY);
    $from_sql = $from_user_id ? (int)$from_user_id : 'NULL';
    $to_sql = $to_user_id ? (int)$to_user_id : 'NULL';
    $amount = (int)$amount;
    $fee_amount = (int)$fee_amount;
    $from_bucket_sql = ($from_bucket === null) ? 'NULL' : "'" . wallet_sql_escape($from_bucket) . "'";
    $to_bucket_sql = ($to_bucket === null) ? 'NULL' : "'" . wallet_sql_escape($to_bucket) . "'";
    $memo_sql = wallet_sql_escape($memo);
    $ref_type_sql = ($ref_type === null || $ref_type === '') ? 'NULL' : "'" . wallet_sql_escape($ref_type) . "'";
    $ref_id_sql = $ref_id ? (int)$ref_id : 'NULL';
    $ip = wallet_sql_escape(wallet_now_ip());
    $ua = wallet_sql_escape(wallet_now_user_agent());

    $sql = "INSERT INTO `{$ledger}`
        (`tx_id`, `tx_type`, `from_user_id`, `to_user_id`, `amount`, `fee_amount`, `currency`, `from_bucket`, `to_bucket`, `status`, `memo`, `ref_type`, `ref_id`, `ip_addr`, `user_agent`)
        VALUES
        ('{$tx_id}', '{$tx_type}', {$from_sql}, {$to_sql}, {$amount}, {$fee_amount}, '{$cur}', {$from_bucket_sql}, {$to_bucket_sql}, 'DONE', '{$memo_sql}', {$ref_type_sql}, {$ref_id_sql}, '{$ip}', '{$ua}')";
    sql_query($sql, true, wallet_db());
    return $tx_id;
}

function wallet_inc_bucket($user_id, $bucket, $amount)
{
    $user_id = wallet_require_user_id($user_id);
    $amount = (int)$amount;
    $balance = wallet_table('balance');
    $bucket_sql = wallet_sql_escape($bucket);
    $cur_sql = wallet_sql_escape(AIWK_WALLET_CURRENCY);
    sql_query("UPDATE `{$balance}` SET `balance`=`balance`+({$amount}) WHERE `user_id`={$user_id} AND `currency`='{$cur_sql}' AND `bucket`='{$bucket_sql}'", true, wallet_db());
}

/* 충전: PG 웹훅 검증 후 호출하는 함수. */
function wallet_credit_charge($user_id, $amount, $memo, $ref_type, $ref_id)
{
    $user_id = wallet_require_user_id($user_id);
    $amount = wallet_require_amount($amount);
    $tx_id = wallet_uuid_v4();

    wallet_begin();
    try {
        wallet_lock_balance_rows($user_id);
        wallet_inc_bucket($user_id, AIWK_WALLET_BUCKET_PAID, $amount);
        wallet_add_ledger($tx_id, 'PURCHASE', null, $user_id, $amount, 0, null, AIWK_WALLET_BUCKET_PAID, $memo, $ref_type, $ref_id);
        wallet_commit();
        return $tx_id;
    } catch (Exception $e) {
        wallet_rollback();
        throw $e;
    }
}

/* 보너스 지급: 이벤트/쿠폰성 지급. 기본 양도/환불 정책은 별도 처리한다. */
function wallet_bonus_grant($user_id, $amount, $memo, $ref_type, $ref_id)
{
    $user_id = wallet_require_user_id($user_id);
    $amount = wallet_require_amount($amount);
    $tx_id = wallet_uuid_v4();

    wallet_begin();
    try {
        wallet_lock_balance_rows($user_id);
        wallet_inc_bucket($user_id, AIWK_WALLET_BUCKET_BONUS, $amount);
        wallet_add_ledger($tx_id, 'BONUS', null, $user_id, $amount, 0, null, AIWK_WALLET_BUCKET_BONUS, $memo, $ref_type, $ref_id);
        wallet_commit();
        return $tx_id;
    } catch (Exception $e) {
        wallet_rollback();
        throw $e;
    }
}

/* 내부 차감 우선순위: BONUS 먼저, 부족하면 PAID. 정책 변경 가능. */
function wallet_debit_available($user_id, $amount)
{
    $amount = wallet_require_amount($amount);
    $rows = wallet_lock_balance_rows($user_id);
    $bonus = isset($rows[AIWK_WALLET_BUCKET_BONUS]) ? (int)$rows[AIWK_WALLET_BUCKET_BONUS] : 0;
    $paid = isset($rows[AIWK_WALLET_BUCKET_PAID]) ? (int)$rows[AIWK_WALLET_BUCKET_PAID] : 0;
    if ($bonus + $paid < $amount) throw new Exception(wallet_t('insufficient_balance'));

    $use_bonus = min($bonus, $amount);
    $remain = $amount - $use_bonus;
    if ($use_bonus > 0) wallet_inc_bucket($user_id, AIWK_WALLET_BUCKET_BONUS, -$use_bonus);
    if ($remain > 0) wallet_inc_bucket($user_id, AIWK_WALLET_BUCKET_PAID, -$remain);
    return array('BONUS' => $use_bonus, 'PAID' => $remain);
}

/* 회원 간 이전. 받는 쪽은 PAID bucket으로 받는다. */
function wallet_transfer($from_user_id, $to_user_id, $amount, $memo, $ref_type, $ref_id)
{
    $from_user_id = wallet_require_user_id($from_user_id);
    $to_user_id = wallet_require_user_id($to_user_id);
    $amount = wallet_require_amount($amount);
    if ($from_user_id == $to_user_id) throw new Exception(wallet_t('same_user'));
    $tx_id = wallet_uuid_v4();

    wallet_begin();
    try {
        wallet_ensure_account($from_user_id);
        wallet_ensure_account($to_user_id);
        if ($from_user_id < $to_user_id) {
            wallet_lock_balance_rows($from_user_id);
            wallet_lock_balance_rows($to_user_id);
        } else {
            wallet_lock_balance_rows($to_user_id);
            wallet_lock_balance_rows($from_user_id);
        }
        wallet_debit_available($from_user_id, $amount);
        wallet_inc_bucket($to_user_id, AIWK_WALLET_BUCKET_PAID, $amount);
        wallet_add_ledger($tx_id, 'TRANSFER', $from_user_id, $to_user_id, $amount, 0, 'AVAILABLE', AIWK_WALLET_BUCKET_PAID, $memo, $ref_type, $ref_id);
        wallet_commit();
        return $tx_id;
    } catch (Exception $e) {
        wallet_rollback();
        throw $e;
    }
}

/* 일반 결제: 구매자 -> 판매자, 선택적으로 플랫폼 수수료 지급. */
function wallet_payment($from_user_id, $to_user_id, $amount, $fee_user_id, $fee_amount, $memo, $ref_type, $ref_id)
{
    $from_user_id = wallet_require_user_id($from_user_id);
    $to_user_id = wallet_require_user_id($to_user_id);
    $amount = wallet_require_amount($amount);
    $fee_user_id = $fee_user_id ? (int)$fee_user_id : 0;
    $fee_amount = (int)$fee_amount;
    if ($fee_amount < 0) throw new Exception(wallet_t('invalid_amount'));
    if ($from_user_id == $to_user_id) throw new Exception(wallet_t('same_user'));
    $total = $amount + $fee_amount;
    $tx_id = wallet_uuid_v4();

    wallet_begin();
    try {
        wallet_lock_balance_rows($from_user_id);
        wallet_lock_balance_rows($to_user_id);
        if ($fee_user_id > 0) wallet_lock_balance_rows($fee_user_id);
        wallet_debit_available($from_user_id, $total);
        wallet_inc_bucket($to_user_id, AIWK_WALLET_BUCKET_PAID, $amount);
        if ($fee_user_id > 0 && $fee_amount > 0) wallet_inc_bucket($fee_user_id, AIWK_WALLET_BUCKET_PAID, $fee_amount);
        wallet_add_ledger($tx_id, 'PAYMENT', $from_user_id, $to_user_id, $amount, $fee_amount, 'AVAILABLE', AIWK_WALLET_BUCKET_PAID, $memo, $ref_type, $ref_id);
        wallet_commit();
        return $tx_id;
    } catch (Exception $e) {
        wallet_rollback();
        throw $e;
    }
}

/* 사용 가능한 잔액을 LOCKED로 이동. P2P 작업 에스크로에 사용. */
function wallet_lock($user_id, $amount, $memo, $ref_type, $ref_id)
{
    $user_id = wallet_require_user_id($user_id);
    $amount = wallet_require_amount($amount);
    $tx_id = wallet_uuid_v4();

    wallet_begin();
    try {
        wallet_lock_balance_rows($user_id);
        wallet_debit_available($user_id, $amount);
        wallet_inc_bucket($user_id, AIWK_WALLET_BUCKET_LOCKED, $amount);
        wallet_add_ledger($tx_id, 'LOCK', $user_id, $user_id, $amount, 0, 'AVAILABLE', AIWK_WALLET_BUCKET_LOCKED, $memo, $ref_type, $ref_id);
        wallet_commit();
        return $tx_id;
    } catch (Exception $e) {
        wallet_rollback();
        throw $e;
    }
}

/* LOCKED 금액을 다시 PAID로 해제. */
function wallet_unlock($user_id, $amount, $memo, $ref_type, $ref_id)
{
    $user_id = wallet_require_user_id($user_id);
    $amount = wallet_require_amount($amount);
    $tx_id = wallet_uuid_v4();

    wallet_begin();
    try {
        $rows = wallet_lock_balance_rows($user_id);
        $locked = isset($rows[AIWK_WALLET_BUCKET_LOCKED]) ? (int)$rows[AIWK_WALLET_BUCKET_LOCKED] : 0;
        if ($locked < $amount) throw new Exception(wallet_t('insufficient_balance'));
        wallet_inc_bucket($user_id, AIWK_WALLET_BUCKET_LOCKED, -$amount);
        wallet_inc_bucket($user_id, AIWK_WALLET_BUCKET_PAID, $amount);
        wallet_add_ledger($tx_id, 'UNLOCK', $user_id, $user_id, $amount, 0, AIWK_WALLET_BUCKET_LOCKED, AIWK_WALLET_BUCKET_PAID, $memo, $ref_type, $ref_id);
        wallet_commit();
        return $tx_id;
    } catch (Exception $e) {
        wallet_rollback();
        throw $e;
    }
}

/* LOCKED 금액에서 작업자/플랫폼에 지급. */
function wallet_pay_from_locked($from_user_id, $to_user_id, $amount, $fee_user_id, $fee_amount, $memo, $ref_type, $ref_id)
{
    $from_user_id = wallet_require_user_id($from_user_id);
    $to_user_id = wallet_require_user_id($to_user_id);
    $amount = wallet_require_amount($amount);
    $fee_user_id = $fee_user_id ? (int)$fee_user_id : 0;
    $fee_amount = (int)$fee_amount;
    if ($fee_amount < 0) throw new Exception(wallet_t('invalid_amount'));
    $total = $amount + $fee_amount;
    $tx_id = wallet_uuid_v4();

    wallet_begin();
    try {
        wallet_lock_balance_rows($from_user_id);
        wallet_lock_balance_rows($to_user_id);
        if ($fee_user_id > 0) wallet_lock_balance_rows($fee_user_id);
        $rows = wallet_get_balances($from_user_id);
        if ((int)$rows['LOCKED'] < $total) throw new Exception(wallet_t('insufficient_balance'));
        wallet_inc_bucket($from_user_id, AIWK_WALLET_BUCKET_LOCKED, -$total);
        wallet_inc_bucket($to_user_id, AIWK_WALLET_BUCKET_PAID, $amount);
        if ($fee_user_id > 0 && $fee_amount > 0) wallet_inc_bucket($fee_user_id, AIWK_WALLET_BUCKET_PAID, $fee_amount);
        wallet_add_ledger($tx_id, 'ESCROW_RELEASE', $from_user_id, $to_user_id, $amount, $fee_amount, AIWK_WALLET_BUCKET_LOCKED, AIWK_WALLET_BUCKET_PAID, $memo, $ref_type, $ref_id);
        wallet_commit();
        return $tx_id;
    } catch (Exception $e) {
        wallet_rollback();
        throw $e;
    }
}

/* 관리자 조정. 직접 UPDATE가 아니라 ledger와 함께 기록한다. 음수는 차감. */
function wallet_admin_adjust($user_id, $amount, $memo)
{
    $user_id = wallet_require_user_id($user_id);
    $amount = (int)$amount;
    if ($amount == 0) throw new Exception(wallet_t('invalid_amount'));
    $tx_id = wallet_uuid_v4();

    wallet_begin();
    try {
        wallet_lock_balance_rows($user_id);
        if ($amount < 0) {
            wallet_debit_available($user_id, abs($amount));
            wallet_add_ledger($tx_id, 'ADMIN_ADJUST', $user_id, null, $amount, 0, 'AVAILABLE', null, $memo, 'admin', 0);
        } else {
            wallet_inc_bucket($user_id, AIWK_WALLET_BUCKET_PAID, $amount);
            wallet_add_ledger($tx_id, 'ADMIN_ADJUST', null, $user_id, $amount, 0, null, AIWK_WALLET_BUCKET_PAID, $memo, 'admin', 0);
        }
        wallet_commit();
        return $tx_id;
    } catch (Exception $e) {
        wallet_rollback();
        throw $e;
    }
}

function wallet_ledger_list($user_id, $limit)
{
    $user_id = wallet_require_user_id($user_id);
    $limit = (int)$limit;
    if ($limit <= 0 || $limit > 200) $limit = 50;
    $ledger = wallet_table('ledger');
    $rs = sql_query("SELECT * FROM `{$ledger}` WHERE `from_user_id`={$user_id} OR `to_user_id`={$user_id} ORDER BY `ledger_id` DESC LIMIT {$limit}", true, wallet_db());
    $out = array();
    while ($r = sql_fetch_array($rs)) $out[] = $r;
    return $out;
}
