<?php

namespace App\Services\Agent;

use App\Models\AdminRole;
use App\Models\AdminUser;
use App\Models\AgentNode;
use App\Models\AgentProfile;
use App\Support\AdminUserStatus;
use App\Support\AgentPlatformRole;
use Illuminate\Support\Facades\DB;
use Illuminate\Validation\ValidationException;

final class AgentNodeService
{
    public function __construct(
        private readonly AgentProfileService $agentProfileService,
    ) {}

    /**
     * @param  array{
     *   parent_id: int,
     *   code?: ?string,
     *   name: string,
     *   username: string,
     *   password: string,
     *   email?: ?string,
     *   status?: int,
     *   total_share_rate?: float|int,
     *   credit_limit?: int,
     *   rebate_limit?: float|int,
     *   default_player_rebate?: float|int,
     *   can_grant_extra_rebate?: bool,
     *   can_create_child_agent?: bool,
     *   can_create_player?: bool
     * }  $payload
     */
    public function createChild(AdminUser $actor, array $payload): AgentNode
    {
        if (! $actor->isSuperAdmin()) {
            $this->agentProfileService->assertActorMayCreateChildAgent($actor);
        }

        $parent = AgentNode::query()->findOrFail((int) $payload['parent_id']);
        $this->agentProfileService->assertChildCapabilityGrantsWithinParent($parent, $payload, $actor);
        $name = trim((string) $payload['name']);
        $code = $this->resolveCodeForCreate($parent, $payload['code'] ?? null, (string) ($payload['username'] ?? ''));
        $username = trim((string) ($payload['username'] ?? ''));
        if ($username === '') {
            $username = 'agent_'.$code;
        }
        $password = (string) ($payload['password'] ?? '');
        if ($password === '') {
            if (app()->environment('testing')) {
                $password = '12345678';
            } else {
                throw ValidationException::withMessages([
                    'password' => ['required'],
                ]);
            }
        }
        $email = isset($payload['email']) ? trim((string) $payload['email']) : null;
        $status = (int) ($payload['status'] ?? 1);
        if ($name === '') {
            throw ValidationException::withMessages([
                'name' => ['required'],
            ]);
        }

        if (AgentNode::query()->where('admin_site_id', $parent->admin_site_id)->where('code', $code)->exists()) {
            throw ValidationException::withMessages([
                'code' => ['unique'],
            ]);
        }

        if (AdminUser::query()->where('username', $username)->exists()) {
            throw ValidationException::withMessages([
                'username' => ['unique'],
            ]);
        }

        if ($email !== null && $email !== '' && AdminUser::query()->where('email', $email)->exists()) {
            throw ValidationException::withMessages([
                'email' => ['unique'],
            ]);
        }

        return DB::transaction(function () use ($actor, $parent, $code, $name, $username, $password, $email, $status, $payload): AgentNode {
            $node = AgentNode::query()->create([
                'admin_site_id' => $parent->admin_site_id,
                'parent_id' => $parent->id,
                'path' => '/',
                'depth' => (int) $parent->depth + 1,
                'code' => $code,
                'name' => $name,
                'status' => $status === 0 ? 0 : 1,
                'created_by' => $actor->id,
                'extra_json' => null,
            ]);

            $node->path = (string) $parent->path.$node->id.'/';
            $node->save();

            AgentPlatformRole::resolve();

            $user = AdminUser::query()->create([
                'username' => $username,
                'name' => $name,
                'email' => $email !== '' ? $email : null,
                'password' => $password,
                'status' => AdminUserStatus::fromAgentNodeStatus($status === 0 ? 0 : 1),
            ]);

            DB::table('admin_user_agents')->insert([
                'admin_user_id' => $user->id,
                'agent_node_id' => $node->id,
                'is_primary' => true,
                'granted_at' => now(),
            ]);
            AgentPlatformRole::assignPrimaryOperator($user, $node);

            $profilePayload = [
                'total_share_rate' => (float) ($payload['total_share_rate'] ?? 0),
                'credit_limit' => (int) ($payload['credit_limit'] ?? 0),
                'rebate_limit' => (float) ($payload['rebate_limit'] ?? 0),
                'default_player_rebate' => (float) ($payload['default_player_rebate'] ?? 0),
                'can_grant_extra_rebate' => (bool) ($payload['can_grant_extra_rebate'] ?? false),
                'can_create_child_agent' => (bool) ($payload['can_create_child_agent'] ?? false),
                'can_create_player' => (bool) ($payload['can_create_player'] ?? true),
            ];
            if (array_key_exists('relative_share_rate', $payload)) {
                $profilePayload['relative_share_rate'] = (float) $payload['relative_share_rate'];
            }
            $this->agentProfileService->upsertForNode($node, $profilePayload, $parent);

            return $node->fresh(['adminSite']);
        });
    }

    /**
     * @param  array{
     *   name?: string,
     *   username?: string,
     *   email?: ?string,
     *   password?: ?string,
     *   status?: int
     * }  $payload
     */
    public function update(AgentNode $node, array $payload): AgentNode
    {
        $primaryUser = $this->primaryUserForNode($node);

        if (array_key_exists('name', $payload)) {
            $name = trim((string) $payload['name']);
            if ($name !== '') {
                $node->name = $name;
                if ($primaryUser instanceof AdminUser) {
                    $primaryUser->name = $name;
                }
            }
        }

        if (array_key_exists('username', $payload)) {
            $username = trim((string) $payload['username']);
            if ($username === '') {
                throw ValidationException::withMessages([
                    'username' => ['required'],
                ]);
            }

            if ($primaryUser instanceof AdminUser) {
                if ($username !== $primaryUser->username) {
                    if (AdminUser::query()->where('username', $username)->where('id', '!=', $primaryUser->id)->exists()) {
                        throw ValidationException::withMessages(['username' => ['unique']]);
                    }
                    $primaryUser->username = $username;
                }
            } else {
                $password = (string) ($payload['password'] ?? '');
                if ($password === '') {
                    if (app()->environment('testing')) {
                        $password = '12345678';
                    } else {
                        throw ValidationException::withMessages([
                            'password' => ['required'],
                        ]);
                    }
                }

                $email = array_key_exists('email', $payload)
                    ? ($payload['email'] !== null ? trim((string) $payload['email']) : null)
                    : null;
                $status = array_key_exists('status', $payload)
                    ? ((int) $payload['status'] === 0 ? 0 : 1)
                    : ((int) $node->status === 0 ? 0 : 1);

                $primaryUser = $this->provisionPrimaryOwnerAccount(
                    $node,
                    $username,
                    $password,
                    $email,
                    $status,
                );
                if ($node->name !== '') {
                    $primaryUser->name = $node->name;
                }
            }
        }

        if (array_key_exists('email', $payload) && $primaryUser instanceof AdminUser) {
            $email = $payload['email'] !== null ? trim((string) $payload['email']) : null;
            if ($email !== null && $email !== '' && AdminUser::query()->where('email', $email)->where('id', '!=', $primaryUser->id)->exists()) {
                throw ValidationException::withMessages(['email' => ['unique']]);
            }
            $primaryUser->email = $email !== '' ? $email : null;
        }

        if (array_key_exists('password', $payload) && $primaryUser instanceof AdminUser) {
            $password = (string) ($payload['password'] ?? '');
            if ($password !== '') {
                $primaryUser->password = $password;
            }
        }

        if (array_key_exists('status', $payload)) {
            $node->status = (int) $payload['status'] === 0 ? 0 : 1;
            if ($primaryUser instanceof AdminUser) {
                $primaryUser->status = AdminUserStatus::fromAgentNodeStatus($node->status);
            }
            AdminRole::query()
                ->where('owner_agent_id', $node->id)
                ->update(['status' => $node->status]);
        }

        $node->save();
        if ($primaryUser instanceof AdminUser) {
            $primaryUser->save();
        }

        return $node->fresh(['adminSite']);
    }

    public function destroy(AgentNode $node): void
    {
        DB::transaction(static function () use ($node): void {
            $userIds = DB::table('admin_user_agents')
                ->where('agent_node_id', $node->id)
                ->pluck('admin_user_id')
                ->map(static fn ($id): int => (int) $id)
                ->all();

            if ($userIds !== []) {
                DB::table('admin_user_agent_roles')
                    ->where('agent_node_id', $node->id)
                    ->whereIn('admin_user_id', $userIds)
                    ->delete();

                DB::table('admin_user_agents')
                    ->where('agent_node_id', $node->id)
                    ->whereIn('admin_user_id', $userIds)
                    ->delete();

                DB::table('admin_user_site_roles')
                    ->whereIn('admin_user_id', $userIds)
                    ->where('site_id', $node->admin_site_id)
                    ->delete();

                AdminUser::query()->whereIn('id', $userIds)->delete();
            }

            DB::table('admin_role_menu_actions')
                ->whereIn(
                    'role_id',
                    AdminRole::query()->where('owner_agent_id', $node->id)->pluck('id')->all(),
                )
                ->delete();

            AdminRole::query()
                ->where('owner_agent_id', $node->id)
                ->delete();

            $node->delete();
        });
    }

    public function hasBlockingCustomRoles(AgentNode $node): bool
    {
        return AdminRole::query()
            ->where('owner_agent_id', $node->id)
            ->whereNull('delegated_from_role_id')
            ->exists();
    }

    private function primaryUserForNode(AgentNode $node): ?AdminUser
    {
        $userId = DB::table('admin_user_agents')
            ->where('agent_node_id', $node->id)
            ->orderByDesc('is_primary')
            ->orderBy('admin_user_id')
            ->value('admin_user_id');

        if ($userId === null) {
            return null;
        }

        return AdminUser::query()->find((int) $userId);
    }

    private function provisionPrimaryOwnerAccount(
        AgentNode $node,
        string $username,
        string $password,
        ?string $email,
        int $status,
    ): AdminUser {
        if (AdminUser::query()->where('username', $username)->exists()) {
            throw ValidationException::withMessages(['username' => ['unique']]);
        }

        if ($email !== null && $email !== '' && AdminUser::query()->where('email', $email)->exists()) {
            throw ValidationException::withMessages(['email' => ['unique']]);
        }

        return DB::transaction(function () use ($node, $username, $password, $email, $status): AdminUser {
            AgentPlatformRole::resolve();

            $user = AdminUser::query()->create([
                'username' => $username,
                'name' => $node->name !== '' ? $node->name : $username,
                'email' => $email !== null && $email !== '' ? $email : null,
                'password' => $password,
                'status' => AdminUserStatus::fromAgentNodeStatus($status),
            ]);

            DB::table('admin_user_agents')->insert([
                'admin_user_id' => $user->id,
                'agent_node_id' => $node->id,
                'is_primary' => true,
                'granted_at' => now(),
            ]);
            AgentPlatformRole::assignPrimaryOperator($user, $node);

            return $user;
        });
    }

    private function resolveCodeForCreate(AgentNode $parent, mixed $rawCode, string $username): string
    {
        $preferred = trim((string) $rawCode);
        if ($preferred !== '') {
            if (AgentNode::query()->where('admin_site_id', $parent->admin_site_id)->where('code', $preferred)->exists()) {
                throw ValidationException::withMessages([
                    'code' => ['unique'],
                ]);
            }

            return $preferred;
        }

        $base = preg_replace('/[^a-zA-Z0-9_-]+/', '_', $username) ?? '';
        $base = trim($base, '_');
        if ($base === '') {
            $base = 'agent';
        }
        $base = substr($base, 0, 64);

        $candidate = $base;
        $suffix = 2;
        while (AgentNode::query()->where('admin_site_id', $parent->admin_site_id)->where('code', $candidate)->exists()) {
            $suffixText = '_'.$suffix;
            $candidate = substr($base, 0, max(1, 64 - strlen($suffixText))).$suffixText;
            $suffix++;
        }

        return $candidate;
    }
}
