<?php

namespace App\Services\Agent;

use App\Models\AdminUser;
use App\Models\AgentNode;
use App\Models\AgentProfile;
use App\Support\AdminAgentScope;
use App\Support\AgentOverdueGuard;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

final class AgentProfileService
{
    public function __construct(
        private readonly ShareRateValidator $shareRateValidator,
        private readonly CreditAllocationValidator $creditAllocationValidator,
        private readonly RebateLimitValidator $rebateLimitValidator,
        private readonly AgentCreditAllocatedSyncService $allocatedSync,
    ) {}

    /**
     * @param  array<string, mixed>  $payload
     */
    public function upsertForNode(AgentNode $node, array $payload, ?AgentNode $parent = null): AgentProfile
    {
        $parent = $parent ?? ($node->parent_id !== null ? AgentNode::query()->find($node->parent_id) : null);

        $existingProfile = AgentProfile::query()->where('agent_node_id', $node->id)->first();

        $totalShare = array_key_exists('total_share_rate', $payload)
            ? (float) $payload['total_share_rate']
            : (float) ($existingProfile->total_share_rate ?? 0);
        $creditLimit = array_key_exists('credit_limit', $payload)
            ? (int) $payload['credit_limit']
            : (int) ($existingProfile->credit_limit ?? 0);
        $rebateLimit = array_key_exists('rebate_limit', $payload)
            ? (float) $payload['rebate_limit'] / 100
            : (float) ($existingProfile->rebate_limit ?? 0);
        $defaultRebate = array_key_exists('default_player_rebate', $payload)
            ? (float) $payload['default_player_rebate'] / 100
            : (float) ($existingProfile->default_player_rebate ?? 0);
        $settlementCycle = array_key_exists('settlement_cycle', $payload)
            ? (string) $payload['settlement_cycle']
            : (string) ($existingProfile->settlement_cycle ?? config('agent_line_defaults.settlement_cycle', 'weekly'));
        if ($settlementCycle === '') {
            $settlementCycle = 'weekly';
        }

        $useRelative = $parent !== null && array_key_exists('relative_share_rate', $payload);

        // 如果提供了相对占成比例，计算绝对总占成
        if ($useRelative) {
            $relativeShare = (float) $payload['relative_share_rate'];
            $this->shareRateValidator->assertRelativeShareWithinBounds($relativeShare);
            $parentRate = $this->shareRateValidator->totalShareRateForNode($parent);
            $totalShare = round($parentRate * $relativeShare / 100, 2);
        }

        if ($parent !== null) {
            $this->shareRateValidator->assertChildWithinParent(
                $parent,
                $totalShare,
                $useRelative ? 'relative_share_rate' : 'total_share_rate',
            );
        }

        return DB::transaction(function () use ($node, $payload, $parent, $totalShare, $creditLimit, $rebateLimit, $defaultRebate, $settlementCycle): AgentProfile {
            $profile = AgentProfile::query()->firstOrNew(['agent_node_id' => $node->id]);
            $previousCredit = (int) $profile->credit_limit;
            $isNew = ! $profile->exists;

            if ($parent !== null && ! $isNew) {
                $this->allocatedSync->syncForAgent($parent);
            }

            if (! $isNew) {
                $this->allocatedSync->syncForAgent($node);
                if ($creditLimit < (int) $profile->allocated_credit) {
                    throw ValidationException::withMessages([
                        'credit_limit' => ['below_allocated'],
                    ]);
                }
            }

            if ($parent !== null) {
                $this->creditAllocationValidator->assertChildCreditLimitWithinParent(
                    $parent,
                    $isNew ? 0 : $previousCredit,
                    $creditLimit,
                    $isNew,
                );
            }

            if ($defaultRebate > $rebateLimit) {
                throw ValidationException::withMessages([
                    'default_player_rebate' => ['exceeds_limit'],
                ]);
            }

            if ($parent !== null) {
                $this->rebateLimitValidator->assertChildRebateLimitWithinParent($parent, $rebateLimit);
            }

            $profile->fill([
                'total_share_rate' => $totalShare,
                'credit_limit' => $creditLimit,
                'rebate_limit' => $rebateLimit,
                'default_player_rebate' => $defaultRebate,
                'can_grant_extra_rebate' => (bool) ($payload['can_grant_extra_rebate'] ?? $profile->can_grant_extra_rebate ?? false),
                'can_create_child_agent' => (bool) ($payload['can_create_child_agent'] ?? ($isNew ? false : $profile->can_create_child_agent)),
                'can_create_player' => (bool) ($payload['can_create_player'] ?? ($isNew ? true : $profile->can_create_player ?? true)),
                'settlement_cycle' => $settlementCycle,
            ]);
            if (! $profile->exists) {
                $profile->allocated_credit = 0;
                $profile->used_credit = 0;
            }
            $profile->save();

            if ($parent !== null) {
                $this->allocatedSync->syncForAgent($parent);
            }

            return $profile;
        });
    }

    /**
     * @return array<string, mixed>
     */
    public function present(AgentProfile $profile): array
    {
        $this->allocatedSync->syncForAgentId((int) $profile->agent_node_id);
        $profile->refresh();

        $available = max(0, (int) $profile->credit_limit - (int) $profile->allocated_credit);

        return [
            'agent_node_id' => (int) $profile->agent_node_id,
            'total_share_rate' => (float) $profile->total_share_rate,
            'credit_limit' => (int) $profile->credit_limit,
            'allocated_credit' => (int) $profile->allocated_credit,
            'used_credit' => (int) $profile->used_credit,
            'available_credit' => $available,
            'rebate_limit' => round((float) $profile->rebate_limit * 100, 4),
            'default_player_rebate' => round((float) $profile->default_player_rebate * 100, 4),
            'can_grant_extra_rebate' => (bool) $profile->can_grant_extra_rebate,
            'can_create_child_agent' => (bool) $profile->can_create_child_agent,
            'can_create_player' => (bool) $profile->can_create_player,
            'settlement_cycle' => (string) ($profile->settlement_cycle ?? 'weekly'),
        ];
    }

    public function profileForNode(int $agentNodeId): ?AgentProfile
    {
        return AgentProfile::query()->where('agent_node_id', $agentNodeId)->first();
    }

    /**
     * @return array<string, mixed>|null
     */
    public function parentCapsForNode(?AgentNode $parent): ?array
    {
        if ($parent === null) {
            return null;
        }

        $this->allocatedSync->syncForAgent($parent);

        $profile = $this->profileForNode((int) $parent->id);
        if ($profile === null) {
            return null;
        }

        return [
            'agent_node_id' => (int) $parent->id,
            'total_share_rate' => (float) $profile->total_share_rate,
            'rebate_limit' => round((float) $profile->rebate_limit * 100, 4),
            'available_credit' => max(0, (int) $profile->credit_limit - (int) $profile->allocated_credit),
        ];
    }

    /** 玩家授信写入前：校验代理可下发是否足够（按当前库内已占用重算）。 */
    public function assertMayIncreasePlayerCredit(AgentNode $agent, int $additionalCredit): void
    {
        if ($additionalCredit <= 0) {
            return;
        }

        $this->assertAgentProfileExists($agent);
        $this->allocatedSync->syncForAgent($agent);
        $this->creditAllocationValidator->assertPlayerCreditDeltaWithinAgent($agent, $additionalCredit);
    }

    /** 玩家授信变更后：按直属玩家+直属下级代理重算已下发额度（无 profile 时跳过）。 */
    public function refreshAllocatedCredit(AgentNode $agent): void
    {
        $this->allocatedSync->syncForAgent($agent);
    }

    public function adjustPlayerCreditAllocation(AgentNode $agent, int $previousLimit, int $newLimit, int $playerUsedCredit = 0): void
    {
        if ($newLimit < $playerUsedCredit) {
            throw ValidationException::withMessages([
                'credit_limit' => ['below_player_used'],
            ]);
        }

        $delta = $newLimit - $previousLimit;
        $this->assertAgentProfileExists($agent);
        $this->allocatedSync->syncForAgent($agent);

        if ($delta > 0) {
            $this->creditAllocationValidator->assertPlayerCreditDeltaWithinAgent($agent, $delta);
        }
    }

    public function assertActorMayCreateChildAgent(AdminUser $admin): void
    {
        if ($admin->isSuperAdmin()) {
            return;
        }

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

        if (! $this->nodeMayCreateChildAgent($node->id)) {
            throw ValidationException::withMessages([
                'parent_id' => ['cannot_create_child_agent'],
            ]);
        }

        if (AgentOverdueGuard::agentHasOverdueBills((int) $node->id)) {
            throw ValidationException::withMessages([
                'parent_id' => ['agent_overdue'],
            ]);
        }
    }

    public function assertActorMayCreatePlayer(AdminUser $admin): void
    {
        if ($admin->isSuperAdmin()) {
            return;
        }

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

        if (! $this->nodeMayCreatePlayer($node->id)) {
            throw ValidationException::withMessages([
                'site_code' => ['cannot_create_player'],
            ]);
        }

        if (AgentOverdueGuard::agentHasOverdueBills((int) $node->id)) {
            throw ValidationException::withMessages([
                'site_code' => ['agent_overdue'],
            ]);
        }
    }

    /**
     * @param  array<string, mixed>  $childPayload
     */
    public function assertChildCapabilityGrantsWithinParent(AgentNode $parent, array $childPayload, AdminUser $actor): void
    {
        if ($actor->isSuperAdmin() || \App\Support\AdminAgentSettlementScope::canManageSitePeriods($actor)) {
            return;
        }

        $parentProfile = $this->profileForNode((int) $parent->id);
        if ((bool) ($childPayload['can_create_child_agent'] ?? false)
            && ! ($parentProfile?->can_create_child_agent ?? false)) {
            throw ValidationException::withMessages([
                'can_create_child_agent' => ['parent_cannot_delegate'],
            ]);
        }

        if ((bool) ($childPayload['can_create_player'] ?? true)
            && ! ($parentProfile?->can_create_player ?? false)) {
            throw ValidationException::withMessages([
                'can_create_player' => ['parent_cannot_delegate'],
            ]);
        }
    }

    public function nodeMayCreateChildAgent(int $agentNodeId): bool
    {
        $profile = $this->profileForNode($agentNodeId);

        return (bool) ($profile?->can_create_child_agent ?? false);
    }

    public function nodeMayCreatePlayer(int $agentNodeId): bool
    {
        $profile = $this->profileForNode($agentNodeId);

        return (bool) ($profile?->can_create_player ?? false);
    }

    private function assertAgentProfileExists(AgentNode $agent): void
    {
        if (AgentProfile::query()->where('agent_node_id', $agent->id)->exists()) {
            return;
        }

        throw ValidationException::withMessages([
            'credit_limit' => ['agent_profile_required'],
        ]);
    }
}
