<?php

namespace App\Services\AgentSettlement;

use App\Models\AgentNode;
use Illuminate\Support\Facades\DB;

final class AgentPeriodAggregator
{
    public function __construct(
        private readonly ShareSettlementCalculator $calculator,
    ) {}

    /**
     * @return array{
     *   players: array<int, array<string, mixed>>,
     *   agent_edges: array<string, int>,
     *   agent_subtrees: array<int, array<string, mixed>>,
     *   platform_share_profit: int,
     * }
     */
    public function aggregate(int $adminSiteId, string $periodStart, string $periodEnd): array
    {
        $siteCode = (string) DB::table('admin_sites')->where('id', $adminSiteId)->value('code');
        $codeToId = AgentNode::query()
            ->where('admin_site_id', $adminSiteId)
            ->pluck('id', 'code')
            ->mapWithKeys(fn ($id, $code): array => [(string) $code => (int) $id])
            ->all();

        $rows = DB::table('share_ledger as sl')
            ->join('players as p', 'p.id', '=', 'sl.player_id')
            ->where('p.site_code', $siteCode)
            ->whereNull('sl.settlement_period_id')
            ->whereBetween('sl.settled_at', [$periodStart, $periodEnd])
            ->select([
                'sl.player_id',
                'sl.ticket_item_id',
                'sl.agent_node_id',
                'sl.share_snapshot',
                'sl.game_win_loss',
                'sl.basic_rebate',
            ])
            ->orderBy('sl.id')
            ->get();

        $players = [];
        $agentEdges = [];
        $agentSubtrees = [];
        $platformShareProfit = 0;

        foreach ($rows as $row) {
            $playerId = (int) $row->player_id;
            $snapshot = $this->resolveSnapshotFromLedgerRow($row);
            if ($snapshot === null) {
                throw new \InvalidArgumentException(
                    'share_snapshot_missing:ticket_item_id='.(int) $row->ticket_item_id,
                );
            }

            $gameWinLoss = (int) $row->game_win_loss;
            $basicRebate = (int) $row->basic_rebate;
            $extraRebate = $this->extraRebateForTicketItem(
                (int) $row->ticket_item_id,
                $periodStart,
                $periodEnd,
            );

            $extraByCode = [];
            if ($extraRebate > 0 && $snapshot['chain_codes'] !== []) {
                $extraByCode[$snapshot['chain_codes'][0]] = $extraRebate;
            }

            $result = $this->calculator->calculate(
                sharedNetWinLoss: 0,
                totalSharesByCode: $snapshot['total_shares'],
                extraRebateByCode: $extraByCode,
                gameWinLoss: $gameWinLoss,
                basicRebate: $basicRebate,
                chainFromPlayer: $snapshot['chain_codes'],
            );

            $net = (int) round($result->playerNetSettlement);

            if (! isset($players[$playerId])) {
                $players[$playerId] = [
                    'agent_node_id' => (int) $row->agent_node_id,
                    'game_win_loss' => 0,
                    'basic_rebate' => 0,
                    'extra_rebate' => 0,
                    'net_amount' => 0,
                ];
            }

            $players[$playerId]['game_win_loss'] += $gameWinLoss;
            $players[$playerId]['basic_rebate'] += $basicRebate;
            $players[$playerId]['extra_rebate'] += $extraRebate;
            $players[$playerId]['net_amount'] += $net;

            foreach ($result->tierSettlements as $edge => $amount) {
                $agentEdges[$edge] = ($agentEdges[$edge] ?? 0) + (int) round($amount);
            }

            $platformShareProfit += (int) round($result->finalProfits['platform'] ?? 0);

            $pathIds = $this->resolveAgentPathIds($row, $snapshot, $codeToId);
            foreach ($pathIds as $agentId) {
                if (! isset($agentSubtrees[$agentId])) {
                    $agentSubtrees[$agentId] = [
                        'gross_win_loss' => 0,
                        'basic_rebate' => 0,
                        'extra_rebate' => 0,
                        'share_profit' => 0,
                        'player_count' => 0,
                        '_players_seen' => [],
                    ];
                }
                $agentSubtrees[$agentId]['gross_win_loss'] += $gameWinLoss;
                $agentSubtrees[$agentId]['basic_rebate'] += $basicRebate;
                $agentSubtrees[$agentId]['extra_rebate'] += $extraRebate;
                if (! in_array($playerId, $agentSubtrees[$agentId]['_players_seen'], true)) {
                    $agentSubtrees[$agentId]['_players_seen'][] = $playerId;
                    $agentSubtrees[$agentId]['player_count']++;
                }
            }

            foreach ($snapshot['chain_codes'] as $code) {
                $agentId = $codeToId[$code] ?? 0;
                if ($agentId <= 0) {
                    continue;
                }
                $profit = (int) round($result->finalProfits[$code] ?? 0);
                $agentSubtrees[$agentId]['share_profit'] = ($agentSubtrees[$agentId]['share_profit'] ?? 0) + $profit;
            }
        }

        foreach ($agentSubtrees as $id => $subtree) {
            unset($agentSubtrees[$id]['_players_seen']);
        }

        return [
            'players' => $players,
            'agent_edges' => $agentEdges,
            'agent_subtrees' => $agentSubtrees,
            'platform_share_profit' => $platformShareProfit,
        ];
    }

    /**
     * @param  array{chain_codes: list<string>, total_shares: array<string, float>}  $snapshot
     * @param  array<string, int>  $codeToId
     * @return list<int>
     */
    private function resolveAgentPathIds(object $row, array $snapshot, array $codeToId): array
    {
        $raw = $row->share_snapshot ?? null;
        if ($raw !== null && $raw !== '') {
            $decoded = is_string($raw) ? json_decode($raw, true) : $raw;
            if (is_array($decoded) && is_array($decoded['agent_path'] ?? null)) {
                return array_values(array_map(intval(...), $decoded['agent_path']));
            }
        }

        $ids = [];
        foreach ($snapshot['chain_codes'] as $code) {
            $id = $codeToId[$code] ?? 0;
            if ($id > 0) {
                $ids[] = $id;
            }
        }

        return $ids;
    }

    public function siteIdForPeriod(int $periodId): int
    {
        return (int) DB::table('settlement_periods')->where('id', $periodId)->value('admin_site_id');
    }

    private function extraRebateForTicketItem(int $ticketItemId, string $periodStart, string $periodEnd): int
    {
        if ($ticketItemId <= 0) {
            return 0;
        }

        return (int) DB::table('rebate_records')
            ->where('ticket_item_id', $ticketItemId)
            ->where('rebate_type', 'extra')
            ->whereIn('status', ['accrued', 'in_bill', 'settled', 'reversed'])
            ->whereBetween('created_at', [$periodStart, $periodEnd])
            ->sum('rebate_amount');
    }

    /**
     * @return array{total_shares: array<string, float>, chain_codes: list<string>}|null
     */
    private function resolveSnapshotFromLedgerRow(object $row): ?array
    {
        $raw = $row->share_snapshot ?? null;
        if ($raw === null || $raw === '') {
            return null;
        }

        $decoded = is_string($raw) ? json_decode($raw, true) : $raw;
        if (! is_array($decoded)) {
            return null;
        }

        $totalShares = $decoded['total_shares'] ?? null;
        $chainCodes = $decoded['chain_codes'] ?? null;
        if (! is_array($totalShares) || ! is_array($chainCodes) || $chainCodes === []) {
            return null;
        }

        $shares = [];
        foreach ($totalShares as $code => $rate) {
            $shares[(string) $code] = (float) $rate;
        }

        return [
            'total_shares' => $shares,
            'chain_codes' => array_values(array_map(strval(...), $chainCodes)),
        ];
    }
}
