<?php

namespace App\Support;

use App\Models\AdminUser;
use App\Models\AgentNode;
use Illuminate\Database\Query\Builder;

/** 结算中心账单：站点范围 + 绑定代理仅见直属边（玩家↔直属代理、代理↔直接上下级）。 */
final class AdminAgentSettlementScope
{
    /**
     * @return list<int>|null null = 不限制子树（超管或未绑定代理）
     */
    public static function subtreeAgentNodeIds(AdminUser $admin): ?array
    {
        if ($admin->isSuperAdmin()) {
            return null;
        }

        $actor = AdminAgentScope::primaryAgentNode($admin);
        if ($actor === null) {
            return null;
        }

        $ids = AgentNode::query()
            ->where('path', 'like', $actor->path.'%')
            ->pluck('id')
            ->map(static fn ($id): int => (int) $id)
            ->all();

        return $ids;
    }

    public static function boundAgentNodeId(AdminUser $admin): ?int
    {
        if ($admin->isSuperAdmin()) {
            return null;
        }

        $actor = AdminAgentScope::primaryAgentNode($admin);

        return $actor !== null ? (int) $actor->id : null;
    }

    public static function applyToPeriodsQuery(Builder $query, AdminUser $admin, string $periodsAlias = 'settlement_periods'): void
    {
        $siteIds = $admin->accessibleAdminSiteIds();
        if ($siteIds === null) {
            return;
        }

        if ($siteIds === []) {
            $query->whereRaw('0 = 1');

            return;
        }

        $query->whereIn($periodsAlias.'.admin_site_id', $siteIds);
    }

    public static function applyToBillsQuery(Builder $query, AdminUser $admin, string $billsAlias = 'settlement_bills'): void
    {
        $siteIds = $admin->accessibleAdminSiteIds();
        if ($siteIds === null) {
            self::applyDirectEdgeScopeToBillsQuery($query, $admin, $billsAlias);

            return;
        }

        if ($siteIds === []) {
            $query->whereRaw('0 = 1');

            return;
        }

        $query->whereExists(function (Builder $sub) use ($siteIds, $billsAlias): void {
            $sub->selectRaw('1')
                ->from('settlement_periods')
                ->whereColumn('settlement_periods.id', $billsAlias.'.settlement_period_id')
                ->whereIn('settlement_periods.admin_site_id', $siteIds);
        });

        self::applyDirectEdgeScopeToBillsQuery($query, $admin, $billsAlias);
    }

    /** @deprecated 使用 {@see applyDirectEdgeScopeToBillsQuery} */
    public static function applySubtreeToBillsQuery(Builder $query, AdminUser $admin, string $billsAlias = 'settlement_bills'): void
    {
        self::applyDirectEdgeScopeToBillsQuery($query, $admin, $billsAlias);
    }

    /**
     * 绑定代理：
     * - 玩家账单：仅直属玩家（players.agent_node_id = 本节点）
     * - 代理账单：owner=本节点（向上）或 counterparty=本节点（下级向我结）
     */
    /**
     * 结算中心玩家维度：绑定代理仅见直属玩家；站点财务/超管见全站（由调用方再限 site_code）。
     */
    public static function applyDirectPlayersToAlias(Builder $query, AdminUser $admin, string $alias = 'p'): void
    {
        if ($admin->isSuperAdmin() || self::canManageSitePeriods($admin)) {
            return;
        }

        $actorId = self::boundAgentNodeId($admin);
        if ($actorId === null) {
            $query->whereRaw('0 = 1');

            return;
        }

        if (! \Illuminate\Support\Facades\Schema::hasColumn('players', 'agent_node_id')) {
            return;
        }

        $query->where($alias.'.agent_node_id', $actorId);
    }

    public static function applyDirectEdgeScopeToBillsQuery(Builder $query, AdminUser $admin, string $billsAlias = 'settlement_bills'): void
    {
        $actorId = self::boundAgentNodeId($admin);
        if ($actorId === null) {
            return;
        }

        $query->where(function (Builder $outer) use ($billsAlias, $actorId): void {
            $outer->where(function (Builder $player) use ($billsAlias, $actorId): void {
                $player->where($billsAlias.'.owner_type', 'player')
                    ->whereExists(function (Builder $exists) use ($billsAlias, $actorId): void {
                        $exists->selectRaw('1')
                            ->from('players')
                            ->whereColumn('players.id', $billsAlias.'.owner_id')
                            ->where('players.agent_node_id', $actorId);
                    });
            })->orWhere(function (Builder $agent) use ($billsAlias, $actorId): void {
                $agent->where($billsAlias.'.owner_type', 'agent')
                    ->where(function (Builder $edge) use ($billsAlias, $actorId): void {
                        $edge->where($billsAlias.'.owner_id', $actorId)
                            ->orWhere(function (Builder $incoming) use ($billsAlias, $actorId): void {
                                $incoming->where($billsAlias.'.counterparty_type', 'agent')
                                    ->where($billsAlias.'.counterparty_id', $actorId);
                            })
                            ->orWhere(function (Builder $platform) use ($billsAlias, $actorId): void {
                                $platform->where($billsAlias.'.counterparty_type', 'platform')
                                    ->where($billsAlias.'.owner_id', $actorId);
                            });
                    });
            });
        });
    }

    public static function periodAccessible(AdminUser $admin, int $settlementPeriodId): bool
    {
        $siteIds = $admin->accessibleAdminSiteIds();
        if ($siteIds === null) {
            return true;
        }

        if ($siteIds === []) {
            return false;
        }

        return \Illuminate\Support\Facades\DB::table('settlement_periods')
            ->where('id', $settlementPeriodId)
            ->whereIn('admin_site_id', $siteIds)
            ->exists();
    }

    public static function siteAccessible(AdminUser $admin, int $adminSiteId): bool
    {
        $siteIds = $admin->accessibleAdminSiteIds();
        if ($siteIds === null) {
            return true;
        }

        return in_array($adminSiteId, $siteIds, true);
    }

    /** 绑定代理账号不可开/关全站账期（仅站点财务或超管）。 */
    public static function canManageSitePeriods(AdminUser $admin): bool
    {
        if ($admin->isSuperAdmin()) {
            return true;
        }

        return AdminAgentScope::primaryAgentNode($admin) === null;
    }

    public static function assertCanManageSitePeriods(AdminUser $admin): void
    {
        if (! self::canManageSitePeriods($admin)) {
            abort(403, 'admin.agent_bound_cannot_manage_periods');
        }
    }

    /** 坏账核销 / 补差冲正仅站点财务或超管（绑定代理不可操作）。 */
    public static function canPerformFinanceAdjustments(AdminUser $admin): bool
    {
        return self::canManageSitePeriods($admin);
    }

    public static function assertCanPerformFinanceAdjustments(AdminUser $admin): void
    {
        if (! self::canPerformFinanceAdjustments($admin)) {
            abort(403, 'admin.agent_bound_cannot_finance_adjust');
        }
    }

    public static function billAccessible(AdminUser $admin, int $settlementBillId): bool
    {
        $siteIds = $admin->accessibleAdminSiteIds();
        if ($siteIds !== null && $siteIds === []) {
            return false;
        }

        $bill = \Illuminate\Support\Facades\DB::table('settlement_bills as sb')
            ->join('settlement_periods as sp', 'sp.id', '=', 'sb.settlement_period_id')
            ->where('sb.id', $settlementBillId)
            ->select([
                'sb.owner_type',
                'sb.owner_id',
                'sb.counterparty_type',
                'sb.counterparty_id',
                'sp.admin_site_id',
            ])
            ->first();

        if ($bill === null) {
            return false;
        }

        if ($siteIds !== null && ! in_array((int) $bill->admin_site_id, $siteIds, true)) {
            return false;
        }

        $actorId = self::boundAgentNodeId($admin);
        if ($actorId === null) {
            return true;
        }

        return self::billMatchesDirectEdgeScope($actorId, $bill);
    }

    public static function canOperateBill(AdminUser $admin, int $settlementBillId): bool
    {
        if (! self::billAccessible($admin, $settlementBillId)) {
            return false;
        }

        if (self::canManageSitePeriods($admin)) {
            return true;
        }

        $bill = \Illuminate\Support\Facades\DB::table('settlement_bills')
            ->where('id', $settlementBillId)
            ->select(['owner_type', 'owner_id', 'counterparty_type', 'counterparty_id', 'net_amount'])
            ->first();

        if ($bill === null) {
            return false;
        }

        $actorId = self::boundAgentNodeId($admin);
        if ($actorId === null) {
            return true;
        }

        return self::billOperableByBoundAgent($actorId, $bill);
    }

    public static function assertCanOperateBill(AdminUser $admin, int $settlementBillId): void
    {
        abort_if(! self::billAccessible($admin, $settlementBillId), 404);

        if (self::canManageSitePeriods($admin)) {
            return;
        }

        $bill = \Illuminate\Support\Facades\DB::table('settlement_bills')
            ->where('id', $settlementBillId)
            ->select(['owner_type', 'owner_id', 'counterparty_type', 'counterparty_id', 'net_amount'])
            ->first();

        abort_if($bill === null, 404);

        $actorId = self::boundAgentNodeId($admin);
        abort_if($actorId === null, 403, 'admin.agent_cannot_operate_bill');

        abort_if(
            ! self::billOperableByBoundAgent($actorId, $bill),
            403,
            (string) $bill->owner_type === 'player'
                ? 'admin.agent_cannot_operate_player_bill'
                : 'admin.agent_cannot_operate_bill',
        );
    }

    private static function billMatchesDirectEdgeScope(int $actorId, object $bill): bool
    {
        if ((string) $bill->owner_type === 'player') {
            $agentNodeId = (int) (\Illuminate\Support\Facades\DB::table('players')
                ->where('id', (int) $bill->owner_id)
                ->value('agent_node_id') ?? 0);

            return $agentNodeId === $actorId;
        }

        if ((string) $bill->owner_type === 'agent') {
            return self::agentBillOnDirectEdge($actorId, $bill);
        }

        return false;
    }

    private static function billOperableByBoundAgent(int $actorId, object $bill): bool
    {
        if ((string) $bill->owner_type === 'player') {
            return (string) $bill->counterparty_type === 'agent'
                && (int) $bill->counterparty_id === $actorId;
        }

        if ((string) $bill->owner_type === 'agent') {
            if (! self::agentBillOnDirectEdge($actorId, $bill)) {
                return false;
            }

            [$payeeType, $payeeId] = self::billPayeeParty($bill);

            return $payeeType === 'agent' && $payeeId === $actorId;
        }

        return false;
    }

    /** net>0：counterparty 为收款方；net<0：owner 为收款方。 */
    private static function billPayeeParty(object $bill): array
    {
        if ((int) $bill->net_amount < 0) {
            return [(string) $bill->owner_type, (int) $bill->owner_id];
        }

        return [(string) $bill->counterparty_type, (int) $bill->counterparty_id];
    }

    private static function agentBillOnDirectEdge(int $actorId, object $bill): bool
    {
        $ownerId = (int) $bill->owner_id;
        $counterType = (string) $bill->counterparty_type;
        $counterId = (int) $bill->counterparty_id;

        if ($ownerId === $actorId) {
            return true;
        }

        if ($counterType === 'agent' && $counterId === $actorId) {
            return true;
        }

        return $counterType === 'platform' && $ownerId === $actorId;
    }
}
