<?php

namespace App\Services\AgentSettlement;

use App\Models\AdminUser;
use App\Support\AdminAgentScope;
use App\Support\AdminDataScope;
use Illuminate\Database\Query\Builder;
use Illuminate\Support\Facades\DB;

/** 按登录视角（平台 / 绑定代理）汇总占成流水 allocations 中的本级输赢。 */
final class ShareLedgerScopedProfitAggregator
{
    public function __construct(
        private readonly ShareSettlementCalculator $calculator,
    ) {}

    /**
     * @return array{key: string, scope: 'platform'|'agent'}
     */
    public function resolveViewer(?AdminUser $admin): array
    {
        if ($admin === null) {
            return ['key' => 'platform', 'scope' => 'platform'];
        }

        $node = AdminAgentScope::primaryAgentNode($admin);
        if ($node === null) {
            return ['key' => 'platform', 'scope' => 'platform'];
        }

        return ['key' => (string) $node->code, 'scope' => 'agent'];
    }

    public function sumForShareQuery(Builder $shareQuery, string $profitKey): int
    {
        $rows = (clone $shareQuery)
            ->select([
                'sl.allocations_json',
                'sl.game_win_loss',
                'sl.basic_rebate',
                'sl.share_snapshot',
            ])
            ->get();

        $total = 0;
        foreach ($rows as $row) {
            $total += $this->profitFromRow($row, $profitKey);
        }

        return $total;
    }

    public function sumRawGameWinLoss(Builder $shareQuery): int
    {
        return (int) ((clone $shareQuery)->sum('sl.game_win_loss') ?? 0);
    }

    /** 绑定代理账号：区间内本级占成收益合计（minor）。 */
    public function sumShareProfitForAdmin(AdminUser $admin, string $dateFrom, string $dateTo): int
    {
        $viewer = $this->resolveViewer($admin);
        if ($viewer['scope'] !== 'agent') {
            return 0;
        }

        return $this->sumForShareQuery(
            $this->shareLedgerBaseQuery($admin, $dateFrom, $dateTo),
            $viewer['key'],
        );
    }

    /**
     * @return array<string, int> business_date (Y-m-d) => share_profit_minor
     */
    public function shareProfitByBusinessDate(AdminUser $admin, string $dateFrom, string $dateTo): array
    {
        $viewer = $this->resolveViewer($admin);
        if ($viewer['scope'] !== 'agent') {
            return [];
        }

        $rows = $this->shareLedgerBaseQuery($admin, $dateFrom, $dateTo)
            ->selectRaw('DATE(sl.settled_at) as business_date')
            ->addSelect(['sl.allocations_json', 'sl.game_win_loss', 'sl.basic_rebate', 'sl.share_snapshot'])
            ->get();

        $byDate = [];
        foreach ($rows as $row) {
            $date = (string) $row->business_date;
            $byDate[$date] = ($byDate[$date] ?? 0) + $this->profitFromRow($row, $viewer['key']);
        }

        return $byDate;
    }

    private function shareLedgerBaseQuery(AdminUser $admin, string $dateFrom, string $dateTo): Builder
    {
        $query = DB::table('share_ledger as sl')
            ->join('players as p', 'p.id', '=', 'sl.player_id')
            ->whereNull('sl.reversal_of_id')
            ->whereDate('sl.settled_at', '>=', $dateFrom)
            ->whereDate('sl.settled_at', '<=', $dateTo);
        AdminDataScope::applyToPlayersAlias($query, $admin, 'p');

        return $query;
    }

    private function profitFromRow(object $row, string $profitKey): int
    {
        $allocations = $this->decodeJsonObject($row->allocations_json ?? null);
        if (array_key_exists($profitKey, $allocations)) {
            return (int) round((float) $allocations[$profitKey]);
        }

        $snapshot = $this->resolveSnapshot($row->share_snapshot ?? null);
        if ($snapshot === null) {
            return 0;
        }

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

        return (int) round($result->finalProfits[$profitKey] ?? 0);
    }

    /**
     * @return array<string, float|int>
     */
    private function decodeJsonObject(mixed $raw): array
    {
        if ($raw === null || $raw === '') {
            return [];
        }

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

        return $decoded;
    }

    /**
     * @return array{total_shares: array<string, float>, chain_codes: list<string>}|null
     */
    private function resolveSnapshot(mixed $raw): ?array
    {
        $decoded = $this->decodeJsonObject($raw);
        if ($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)),
        ];
    }
}
