<?php

namespace App\Support;

use App\Lottery\ErrorCode;
use App\Models\AdminSite;
use App\Models\AdminUser;
use App\Models\AgentNode;
use App\Models\Player;
use App\Models\ReconcileJob;
use App\Support\ApiMessage;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Http\JsonResponse;

/** 对账任务：按接入站点（及绑定代理子树）收敛可见范围。 */
final class AdminReconcileScope
{
    /**
     * @param  Builder<ReconcileJob>  $query
     */
    public static function applyToJobsQuery(Builder $query, AdminUser $admin): void
    {
        if ($admin->isSuperAdmin()) {
            return;
        }

        $siteIds = $admin->accessibleAdminSiteIds();
        if ($siteIds === null) {
            return;
        }

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

            return;
        }

        $siteCodes = AdminSite::query()
            ->whereIn('id', $siteIds)
            ->pluck('code')
            ->map(static fn ($code): string => (string) $code)
            ->all();

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

        $query->where(function (Builder $outer) use ($siteIds, $siteCodes, $agent): void {
            $outer->whereIn('admin_site_id', $siteIds);

            if ($siteCodes !== []) {
                $outer->orWhere(function (Builder $legacy) use ($siteCodes, $agent): void {
                    $legacy->whereNull('admin_site_id')
                        ->whereHas('items', function (Builder $items) use ($siteCodes, $agent): void {
                            self::applyItemsTransferPlayerScope($items, $siteCodes, $agent);
                        });
                });
            }
        });

        if ($agent instanceof AgentNode) {
            $query->whereHas('items', function (Builder $items) use ($agent): void {
                self::applyItemsAgentSubtree($items, $agent);
            });
        }
    }

    public static function jobAccessible(AdminUser $admin, ReconcileJob $job): bool
    {
        if ($admin->isSuperAdmin()) {
            return true;
        }

        $siteIds = $admin->accessibleAdminSiteIds();
        if ($siteIds === null) {
            return true;
        }

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

        $jobSiteId = $job->admin_site_id !== null ? (int) $job->admin_site_id : null;
        if ($jobSiteId !== null) {
            if (! in_array($jobSiteId, $siteIds, true)) {
                return false;
            }
        } else {
            $siteCodes = AdminSite::query()
                ->whereIn('id', $siteIds)
                ->pluck('code')
                ->map(static fn ($code): string => (string) $code)
                ->all();

            $hasScopedItem = $job->items()
                ->where(function (Builder $items) use ($siteCodes, $admin): void {
                    $agent = AdminAgentScope::primaryAgentNode($admin);
                    self::applyItemsTransferPlayerScope($items, $siteCodes, $agent);
                })
                ->exists();

            if (! $hasScopedItem) {
                return false;
            }
        }

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

        return $job->items()
            ->where(function (Builder $items) use ($agent): void {
                self::applyItemsAgentSubtree($items, $agent);
            })
            ->exists();
    }

    public static function resolveAdminSiteIdForJob(AdminUser $admin, ?Player $player = null): ?int
    {
        if ($player !== null) {
            $siteId = AdminSite::query()
                ->where('code', (string) $player->site_code)
                ->value('id');

            return $siteId !== null ? (int) $siteId : null;
        }

        if ($admin->isSuperAdmin()) {
            return null;
        }

        $agent = AdminAgentScope::primaryAgentNode($admin);
        if ($agent !== null) {
            return (int) $agent->admin_site_id;
        }

        $siteIds = $admin->accessibleAdminSiteIds();

        return $siteIds[0] ?? null;
    }

    public static function denyUnlessJobAccessible(AdminUser $admin, ReconcileJob $job): ?JsonResponse
    {
        if (! self::jobAccessible($admin, $job)) {
            return ApiMessage::errorResponse(
                request(),
                'admin.reconcile_job_access_denied',
                ErrorCode::AdminForbidden->value,
                null,
                403,
            );
        }

        return null;
    }

    /**
     * @param  Builder<\App\Models\ReconcileItem>  $query
     * @param  list<string>  $siteCodes
     */
    public static function applyItemsTransferPlayerScope(
        Builder $query,
        array $siteCodes,
        ?AgentNode $agent = null,
    ): void {
        if ($siteCodes === []) {
            $query->whereRaw('0 = 1');

            return;
        }

        $query->whereExists(function ($sub) use ($siteCodes, $agent): void {
            $sub->selectRaw('1')
                ->from('transfer_orders')
                ->join('players', 'players.id', '=', 'transfer_orders.player_id')
                ->whereColumn('transfer_orders.transfer_no', 'reconcile_items.side_a_ref');

            $sub->whereIn('players.site_code', $siteCodes);

            if ($agent instanceof AgentNode) {
                $sub->whereIn('players.agent_node_id', function ($nodes) use ($agent): void {
                    $nodes->select('id')
                        ->from('agent_nodes')
                        ->where('path', 'like', $agent->path.'%');
                });
            }
        });
    }

    /**
     * @param  Builder<\App\Models\ReconcileItem>  $query
     */
    private static function applyItemsAgentSubtree(Builder $query, AgentNode $agent): void
    {
        $query->whereExists(function ($sub) use ($agent): void {
            $sub->selectRaw('1')
                ->from('transfer_orders')
                ->join('players', 'players.id', '=', 'transfer_orders.player_id')
                ->whereColumn('transfer_orders.transfer_no', 'reconcile_items.side_a_ref')
                ->whereIn('players.agent_node_id', function ($nodes) use ($agent): void {
                    $nodes->select('id')
                        ->from('agent_nodes')
                        ->where('path', 'like', $agent->path.'%');
                });
        });
    }
}
