<?php

namespace App\Services\Agent;

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

final class AgentAdminUserService
{
    /**
     * @param  array{
     *   username: string,
     *   nickname: string,
     *   email?: ?string,
     *   password: string,
     *   status?: int,
     *   role_ids?: list<int>
     * }  $payload
     */
    public function createUnderAgent(AgentNode $agent, array $payload): AdminUser
    {
        $roleIds = array_values(array_unique(array_map('intval', $payload['role_ids'] ?? [])));
        $this->assertRolesAssignable($agent, $roleIds);

        return DB::transaction(function () use ($agent, $payload, $roleIds): AdminUser {
            $user = AdminUser::query()->create([
                'username' => $payload['username'],
                'name' => $payload['nickname'],
                'email' => isset($payload['email']) ? trim((string) $payload['email']) : null,
                'password' => $payload['password'],
                'status' => (int) ($payload['status'] ?? 0),
            ]);

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

            $user->syncAgentRoleIds($agent->id, $roleIds);

            return $user->fresh();
        });
    }

    /**
     * @param  list<int>  $roleIds
     */
    public function syncRoles(AgentNode $agent, AdminUser $user, array $roleIds): AdminUser
    {
        if ((int) $user->primaryAgentNodeId() !== (int) $agent->id) {
            throw ValidationException::withMessages(['user' => ['agent_mismatch']]);
        }

        $roleIds = array_values(array_unique(array_map('intval', $roleIds)));
        $this->assertRolesAssignable($agent, $roleIds);
        $user->syncAgentRoleIds($agent->id, $roleIds);

        return $user->fresh();
    }

    /**
     * @param  list<int>  $roleIds
     */
    private function assertRolesAssignable(AgentNode $agent, array $roleIds): void
    {
        if ($roleIds === []) {
            return;
        }

        $validCount = AdminRole::query()
            ->where('scope_type', AdminRole::SCOPE_AGENT)
            ->where('owner_agent_id', $agent->id)
            ->whereIn('id', $roleIds)
            ->count();

        if ($validCount !== count($roleIds)) {
            throw ValidationException::withMessages(['role_ids' => ['invalid_for_agent']]);
        }
    }

    public function destroyUnderAgent(AgentNode $agent, AdminUser $user): void
    {
        if ((int) $user->primaryAgentNodeId() !== (int) $agent->id) {
            throw ValidationException::withMessages(['user' => ['agent_mismatch']]);
        }

        DB::transaction(static function () use ($agent, $user): void {
            DB::table('admin_user_agent_roles')
                ->where('admin_user_id', $user->id)
                ->where('agent_node_id', $agent->id)
                ->delete();

            DB::table('admin_user_agents')
                ->where('admin_user_id', $user->id)
                ->where('agent_node_id', $agent->id)
                ->delete();

            $siteId = (int) $agent->admin_site_id;
            DB::table('admin_user_site_roles')
                ->where('admin_user_id', $user->id)
                ->where('site_id', $siteId)
                ->delete();

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