<?php

namespace App\Services\Agent;

use App\Models\AdminUser;
use App\Models\AgentNode;
use App\Support\AgentDelegationAuthorization;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;

final class AgentDelegationService
{
    /**
     * @return list<array{
     *   menu_action_id: int,
     *   permission_code: string,
     *   name: string,
     *   can_delegate: bool
     * }>
     */
    public function listForChild(AgentNode $child, ?AdminUser $actor = null): array
    {
        $rows = DB::table('agent_delegation_grants as g')
            ->join('admin_menu_actions as ma', 'ma.id', '=', 'g.menu_action_id')
            ->where('g.child_agent_id', $child->id)
            ->where('ma.status', 1)
            ->orderBy('ma.permission_code')
            ->get(['g.menu_action_id', 'g.can_delegate', 'ma.permission_code', 'ma.name']);

        if ($rows->isNotEmpty()) {
            return $rows->map(static fn ($row): array => [
                'menu_action_id' => (int) $row->menu_action_id,
                'permission_code' => (string) $row->permission_code,
                'name' => (string) $row->name,
                'can_delegate' => (bool) $row->can_delegate,
            ])->all();
        }

        if ($actor === null) {
            return [];
        }

        $codes = $actor->effectiveMenuActionPermissionCodes();
        if ($codes === []) {
            return [];
        }

        return DB::table('admin_menu_actions')
            ->where('status', 1)
            ->whereIn('permission_code', $codes)
            ->orderBy('permission_code')
            ->get(['id', 'permission_code', 'name'])
            ->map(static fn ($row): array => [
                'menu_action_id' => (int) $row->id,
                'permission_code' => (string) $row->permission_code,
                'name' => (string) $row->name,
                'can_delegate' => false,
            ])
            ->all();
    }

    /**
     * @param  list<array{menu_action_id: int, can_delegate?: bool}>  $grants
     * @return list<array{menu_action_id: int, permission_code: string, name: string, can_delegate: bool}>
     */
    public function syncGrants(AdminUser $actor, AgentNode $child, array $grants): array
    {
        AgentDelegationAuthorization::assertGrantsAllowed($actor, $child, $grants);

        $parentId = (int) ($child->parent_id ?? 0);
        if ($parentId <= 0) {
            throw new \InvalidArgumentException('Child agent must have parent.');
        }

        $now = Carbon::now();

        DB::transaction(function () use ($actor, $child, $grants, $parentId, $now): void {
            DB::table('agent_delegation_grants')
                ->where('child_agent_id', $child->id)
                ->delete();

            foreach ($grants as $grant) {
                DB::table('agent_delegation_grants')->insert([
                    'parent_agent_id' => $parentId,
                    'child_agent_id' => $child->id,
                    'menu_action_id' => (int) $grant['menu_action_id'],
                    'can_delegate' => ! empty($grant['can_delegate']),
                    'granted_by' => $actor->id,
                    'granted_at' => $now,
                    'created_at' => $now,
                    'updated_at' => $now,
                ]);
            }
        });

        return $this->listForChild($child->fresh());
    }
}
