<?php
/**
 * classes/MLM.php
 * Motor completo de Rede MLM: Binário + Unilevel
 * Padrão do sistema: autoload via settings.php
 */
class MLM extends DBConnection
{
    private $settings;

    public function __construct() {
        global $_settings;
        $this->settings = $_settings;
        parent::__construct();
    }

    // ══════════════════════════════════════════════════════
    // ── SEÇÃO 1: POSICIONAMENTO NA ÁRVORE BINÁRIA ─────────
    // ══════════════════════════════════════════════════════

    /**
     * Chamado no momento do cadastro (quando vem via link de indicação)
     * Garante que o usuário seja inserido corretamente na árvore binária
     */
    public function register_in_binary($customer_id, $sponsor_code = null) {
        $cid = (int) $customer_id;

        // Garante nó na árvore (pode já ter sido criado pelo trigger)
        $this->conn->query("INSERT IGNORE INTO binary_tree (customer_id) VALUES ($cid)");

        if (!$sponsor_code) return; // Sem indicação, fica na raiz

        // Resolve sponsor_code → customer_id
        $sponsor_row = $this->conn->query(
            "SELECT customer_id FROM referral WHERE referral_code = '"
            . $this->conn->real_escape_string($sponsor_code) . "'"
        )->fetch_assoc();

        if (!$sponsor_row) return;
        $sponsor_id = (int) $sponsor_row['customer_id'];

        // Garante que o sponsor também tem um nó
        $this->conn->query("INSERT IGNORE INTO binary_tree (customer_id) VALUES ($sponsor_id)");

        // Encontra a posição mais fraca na árvore do sponsor (BFS)
        $position = $this->find_weakest_position($sponsor_id);

        if (!$position) return;

        $parent_id = (int) $position['parent_id'];
        $side      = $position['side'];           // 'left' ou 'right'
        $opp_side  = ($side === 'left') ? 'right' : 'left';

        // Atualiza o nó do novo usuário
        $this->conn->query("UPDATE binary_tree
            SET sponsor_id = $sponsor_id,
                parent_id  = $parent_id,
                side       = '$side'
            WHERE customer_id = $cid");

        // Registra o novo usuário como filho do parent
        $child_col = $side . '_id';
        $this->conn->query("UPDATE binary_tree
            SET $child_col = $cid
            WHERE customer_id = $parent_id");

        // Cria/atualiza o referral do sponsor (reutiliza tabela existente)
        $ref_exists = $this->conn->query(
            "SELECT id FROM referral WHERE customer_id = $sponsor_id"
        )->num_rows;

        if (!$ref_exists) {
            $pct = (float)($this->settings->info('unilevel_rates_default') ?: 10);
            $this->conn->query("INSERT IGNORE INTO referral
                (status, referral_code, percentage, amount_paid, amount_pending, customer_id)
                VALUES (1, $sponsor_id, $pct, 0, 0, $sponsor_id)");
        }
    }

    /**
     * BFS (Breadth-First Search) para encontrar o lado mais fraco
     * Prioridade: lado esquerdo em caso de empate
     */
    private function find_weakest_position($root_id) {
        $queue   = [(int)$root_id];
        $visited = [];

        while (!empty($queue)) {
            $current_id = array_shift($queue);
            if (in_array($current_id, $visited)) continue;
            $visited[] = $current_id;

            $node = $this->conn->query(
                "SELECT * FROM binary_tree WHERE customer_id = $current_id"
            )->fetch_assoc();

            if (!$node) continue;

            // Verifica lado esquerdo
            if (empty($node['left_id'])) {
                return ['parent_id' => $current_id, 'side' => 'left'];
            }
            // Verifica lado direito
            if (empty($node['right_id'])) {
                return ['parent_id' => $current_id, 'side' => 'right'];
            }

            // Continua BFS
            $queue[] = (int)$node['left_id'];
            $queue[] = (int)$node['right_id'];
        }
        return null;
    }

    // ══════════════════════════════════════════════════════
    // ── SEÇÃO 2: ATUALIZAR VOLUMES NA UPLINE ──────────────
    // ══════════════════════════════════════════════════════

    /**
     * Chamado quando um investimento é ativado/rendimento gerado
     * Sobe o volume por toda a upline binária
     */
    public function update_binary_volumes($customer_id, $amount) {
        $cid    = (int) $customer_id;
        $amount = (float) $amount;

        $node = $this->conn->query(
            "SELECT parent_id, side FROM binary_tree WHERE customer_id = $cid"
        )->fetch_assoc();

        if (!$node || !$node['parent_id']) return;

        $parent_id = (int) $node['parent_id'];
        $side      = $node['side'];

        // Sobe pela árvore até a raiz
        $iterations = 0;
        while ($parent_id && $iterations < 50) {
            $col = ($side === 'left') ? 'left_volume' : 'right_volume';
            $this->conn->query(
                "UPDATE binary_tree SET $col = $col + $amount WHERE customer_id = $parent_id"
            );

            // Sobe um nível
            $parent_node = $this->conn->query(
                "SELECT parent_id, side FROM binary_tree WHERE customer_id = $parent_id"
            )->fetch_assoc();

            $side      = $parent_node['side']      ?? null;
            $parent_id = (int)($parent_node['parent_id'] ?? 0);
            $iterations++;
        }
    }

    // ══════════════════════════════════════════════════════
    // ── SEÇÃO 3: PROCESSAR BÔNUS BINÁRIO (CRON) ───────────
    // ══════════════════════════════════════════════════════

    /**
     * Roda via cron (diário ou semanal, configurável)
     * Calcula e paga bônus com base no lado mais fraco ("flush")
     */
    public function process_binary_bonuses() {
        require_once BASE_APP . 'includes/wallet-functions.php';

        $bonus_rate = (float) ($this->settings->info('binary_bonus_rate') ?: 10);
        $log        = [];

        // Busca todos os nós que têm volume nos dois lados
        $nodes = $this->conn->query(
            "SELECT bt.*, c.firstname, c.phone
             FROM binary_tree bt
             JOIN customer_list c ON bt.customer_id = c.id
             WHERE bt.left_volume > 0 AND bt.right_volume > 0"
        );

        while ($node = $nodes->fetch_assoc()) {
            $left  = (float) $node['left_volume'];
            $right = (float) $node['right_volume'];
            $cid   = (int)   $node['customer_id'];

            $weak_side   = min($left, $right);
            $strong_side = max($left, $right);
            $bonus       = round($weak_side * ($bonus_rate / 100), 2);

            if ($bonus < 0.01) continue;

            // Credita na carteira
            wallet_credit($cid, $bonus, 'binary_bonus',
                          "Bônus Binário — Base: R$ " . number_format($weak_side, 2, ',', '.'),
                          null, 'binary');

            // Registra o bônus
            $this->conn->query("INSERT INTO binary_bonuses SET
                customer_id       = $cid,
                weak_side_volume  = $weak_side,
                bonus_rate        = $bonus_rate,
                amount            = $bonus,
                left_volume_used  = $left,
                right_volume_used = $right");

            // FLUSH: zera o volume usado, mantém a sobra do lado forte
            $carry = round($strong_side - $weak_side, 2);
            $new_left  = ($left  >= $right) ? $carry : 0;
            $new_right = ($right >= $left)  ? $carry : 0;
            // Adiciona ao carry histórico tbm
            $this->conn->query("UPDATE binary_tree SET
                left_volume          = $new_left,
                right_volume         = $new_right,
                total_binary_earned  = total_binary_earned + $bonus
                WHERE customer_id = $cid");

            // Notificação WhatsApp
            enqueue_whatsapp($cid, 'binary_bonus_received', $bonus);

            $log[] = [
                'customer_id' => $cid,
                'name'        => $node['firstname'],
                'weak_vol'    => $weak_side,
                'bonus'       => $bonus,
                'carry'       => $carry,
            ];
        }

        return $log;
    }

    // ══════════════════════════════════════════════════════
    // ── SEÇÃO 4: COMISSÕES UNILEVEL ───────────────────────
    // ══════════════════════════════════════════════════════

    /**
     * Paga comissões em cascata para até 10 níveis acima do gerador
     * Chamado quando há rendimento de investimento
     */
    public function process_unilevel_commissions($origin_customer_id, $base_amount, $investment_id = null) {
        require_once BASE_APP . 'includes/wallet-functions.php';

        $origin  = (int)   $origin_customer_id;
        $amount  = (float) $base_amount;

        // Busca as taxas configuradas
        $rates_res = $this->conn->query("SELECT level, rate FROM unilevel_rates ORDER BY level");
        $rates = [];
        while ($r = $rates_res->fetch_assoc()) {
            $rates[(int)$r['level']] = (float)$r['rate'];
        }

        $current_id = $origin;
        $paid_log   = [];

        for ($level = 1; $level <= 10; $level++) {
            // Sobe um nível na árvore (via sponsor_id)
            $sponsor_row = $this->conn->query(
                "SELECT sponsor_id FROM binary_tree WHERE customer_id = $current_id"
            )->fetch_assoc();

            if (!$sponsor_row || !$sponsor_row['sponsor_id']) break;
            $sponsor_id = (int) $sponsor_row['sponsor_id'];

            $rate       = $rates[$level] ?? 0;
            if ($rate <= 0) { $current_id = $sponsor_id; continue; }

            $commission = round($amount * ($rate / 100), 2);
            if ($commission < 0.01) { $current_id = $sponsor_id; continue; }

            // Verifica se o sponsor tem plano ativo (opcional por config)
            $require_plan = (int)($this->settings->info('mlm_require_active_plan') ?: 0);
            if ($require_plan) {
                $has_plan = $this->conn->query(
                    "SELECT id FROM investments WHERE customer_id=$sponsor_id AND status=1"
                )->num_rows;
                if (!$has_plan) { $current_id = $sponsor_id; continue; }
            }

            // Paga a comissão
            wallet_credit($sponsor_id, $commission, 'unilevel_bonus',
                          "Comissão Nível $level — Indicado #$origin",
                          $investment_id, 'investment');

            // Registra
            $this->conn->query("INSERT INTO unilevel_commissions SET
                beneficiary_id     = $sponsor_id,
                origin_customer_id = $origin,
                investment_id      = " . ($investment_id ? $investment_id : 'NULL') . ",
                level              = $level,
                rate               = $rate,
                base_amount        = $amount,
                commission_amount  = $commission");

            // Notifica a cada 5 níveis (para não spammar)
            if (in_array($level, [1, 5, 10])) {
                enqueue_whatsapp($sponsor_id, 'unilevel_received', $commission);
            }

            $paid_log[] = ['level' => $level, 'beneficiary' => $sponsor_id, 'commission' => $commission];
            $current_id = $sponsor_id;
        }

        return $paid_log;
    }

    // ══════════════════════════════════════════════════════
    // ── SEÇÃO 5: DADOS DA REDE PARA EXIBIÇÃO ──────────────
    // ══════════════════════════════════════════════════════

    /**
     * Retorna a rede do usuário formatada para visualização
     */
    public function get_user_network($customer_id, $depth = 3) {
        $cid = (int) $customer_id;

        $root = $this->conn->query(
            "SELECT bt.*, c.firstname, c.lastname, c.phone, c.avatar
             FROM binary_tree bt
             JOIN customer_list c ON bt.customer_id = c.id
             WHERE bt.customer_id = $cid"
        )->fetch_assoc();

        if (!$root) return null;
        return $this->build_tree_node($root, $depth, 0);
    }

    private function build_tree_node($node, $max_depth, $current_depth) {
        $data = [
            'id'         => (int) $node['customer_id'],
            'name'       => $node['firstname'] . ' ' . ($node['lastname'][0] ?? '') . '.',
            'avatar'     => validate_image($node['avatar'] ?? ''),
            'left_vol'   => (float) $node['left_volume'],
            'right_vol'  => (float) $node['right_volume'],
            'total_earn' => (float) $node['total_binary_earned'],
            'left'       => null,
            'right'      => null,
        ];

        if ($current_depth >= $max_depth) return $data;

        // Filho esquerdo
        if (!empty($node['left_id'])) {
            $left_node = $this->conn->query(
                "SELECT bt.*, c.firstname, c.lastname, c.avatar
                 FROM binary_tree bt JOIN customer_list c ON bt.customer_id=c.id
                 WHERE bt.customer_id=" . (int)$node['left_id']
            )->fetch_assoc();
            if ($left_node) {
                $data['left'] = $this->build_tree_node($left_node, $max_depth, $current_depth + 1);
            }
        }

        // Filho direito
        if (!empty($node['right_id'])) {
            $right_node = $this->conn->query(
                "SELECT bt.*, c.firstname, c.lastname, c.avatar
                 FROM binary_tree bt JOIN customer_list c ON bt.customer_id=c.id
                 WHERE bt.customer_id=" . (int)$node['right_id']
            )->fetch_assoc();
            if ($right_node) {
                $data['right'] = $this->build_tree_node($right_node, $max_depth, $current_depth + 1);
            }
        }

        return $data;
    }

    /**
     * Lista de indicados diretos (Nível 1) + contagem por nível
     */
    public function get_direct_referrals($customer_id) {
        $cid = (int) $customer_id;
        return $this->conn->query(
            "SELECT c.id, c.firstname, c.lastname, c.phone, c.avatar,
                    c.date_added,
                    (SELECT COUNT(*) FROM investments WHERE customer_id=c.id AND status=1) as has_plan,
                    (SELECT SUM(amount_invested) FROM investments WHERE customer_id=c.id) as total_invested
             FROM binary_tree bt
             JOIN customer_list c ON bt.customer_id = c.id
             WHERE bt.sponsor_id = $cid
             ORDER BY c.date_added DESC"
        );
    }

    /**
     * Conta membros por nível de profundidade na rede
     */
    public function get_network_depth_count($customer_id) {
        $cid    = (int) $customer_id;
        $counts = [];

        $current_level = [$cid];
        for ($l = 1; $l <= 10; $l++) {
            if (empty($current_level)) break;
            $ids  = implode(',', $current_level);
            $next = $this->conn->query(
                "SELECT customer_id FROM binary_tree
                 WHERE sponsor_id IN ($ids)"
            );
            $next_ids = [];
            while ($r = $next->fetch_assoc()) $next_ids[] = (int)$r['customer_id'];
            $counts[$l] = count($next_ids);
            $current_level = $next_ids;
        }
        return $counts;
    }

    // ══════════════════════════════════════════════════════
    // ── SEÇÃO 6: LINK DE INDICAÇÃO ────────────────────────
    // ══════════════════════════════════════════════════════

    public function ensure_referral_code($customer_id) {
        $cid = (int) $customer_id;
        $existing = $this->conn->query(
            "SELECT referral_code FROM referral WHERE customer_id = $cid"
        )->fetch_assoc();

        if ($existing) return $existing['referral_code'];

        // Cria novo código (usa o ID do usuário como código)
        $this->conn->query("INSERT IGNORE INTO referral
            (status, referral_code, percentage, amount_paid, amount_pending, customer_id)
            VALUES (1, $cid, 10, 0, 0, $cid)");

        return $cid;
    }
}

// ── Roteador de ações ────────────────────────────────────
require_once '../settings.php';
$mlm    = new MLM();
$action = strtolower($_GET['f'] ?? '');

switch ($action) {
    case 'get_network':
        header('Content-Type: application/json');
        $cid  = (int)($_settings->userdata('id') ?? 0);
        if (!$cid) { echo json_encode([]); exit(); }
        echo json_encode($mlm->get_user_network($cid, 3));
        break;

    case 'process_bonuses':
        $secret = $_GET['secret'] ?? '';
        if ($secret === $_settings->info('cron_secret')) {
            $log = $mlm->process_binary_bonuses();
            echo json_encode(['status'=>'ok','processed'=>count($log),'log'=>$log]);
        }
        break;

    default:
        break;
}
