<?php

namespace App\Http\Controllers;

use App\Mail\Workspace\ProjectAccessMail;
use App\Models\Project;
use App\Models\ProjectMember;
use App\Models\Tenant;
use App\Models\User;
use App\Notifications\ProjectAccessRevoked;
use App\Notifications\ProjectShared;
use App\Services\AuditLogger;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Mail;
use Illuminate\Validation\ValidationException;

/**
 * Per-project visibility sharing (Team Workspaces P3d / Δ23 + Δ24 + Δ25).
 * Owner-or-admin grant/revoke of a project to existing workspace members, plus
 * the in-app share notification. JSON-returning (desktop-portable — CLAUDE.md
 * §Core patterns). SaaS-only (mounted in the `saas-only` route group).
 *
 * Authorization is two-layered: the acting user must be owner/admin of the
 * project's tenant (WorkspacePolicy::shareProject/revokeProjectShare), and the
 * grant TARGET must already be a `tenant_user` member of that same tenant
 * (sharing is with existing workspace members — invites are workspace-level).
 */
class ProjectMemberController extends Controller
{
    /**
     * GET /projects/{project}/members
     *
     * SHAPE CONTRACT (the share modal / ProjectMembers UI depends on this):
     *   { project:{id,name},
     *     members:[{id,name,email,role}],     // explicitly granted users
     *     candidates:[{id,name,email,role}] } // workspace editors/viewers
     *                                         //   WITHOUT a grant yet
     *
     * Owner/admins have implicit access (they see all projects) and aren't
     * listed in either bucket — sharing targets are editors/viewers.
     */
    public function index(Request $request, Project $project): JsonResponse
    {
        $tenant = $this->authorizeShare($request, $project);

        $grantedIds = ProjectMember::query()
            ->where('project_id', $project->id)
            ->pluck('user_id')
            ->all();
        $grantedSet = array_flip($grantedIds);

        // Workspace members minus owner/admins (implicit access). Split into
        // granted (members) vs ungranted shareable (candidates).
        $members = [];
        $candidates = [];

        foreach ($tenant->members()->orderBy('users.name')->get() as $u) {
            $role = $u->getRelationValue('pivot')?->role;
            if (in_array($role, ['owner', 'admin'], true)) {
                continue;
            }
            $row = [
                'id' => $u->id,
                'name' => $u->name,
                'email' => $u->email,
                'role' => $role,
            ];
            if (isset($grantedSet[$u->id])) {
                $members[] = $row;
            } else {
                $candidates[] = $row;
            }
        }

        return response()->json([
            'project' => [
                'id' => $project->id,
                'name' => $project->name,
            ],
            'members' => $members,
            'candidates' => $candidates,
        ]);
    }

    /**
     * POST /projects/{project}/members  { user: <id> }
     *
     * Grant a workspace member visibility of the project + raise the in-app
     * notification (Δ25). Idempotent on re-grant. The target MUST be a
     * `tenant_user` member of the project's tenant.
     */
    public function store(Request $request, Project $project): JsonResponse
    {
        $tenant = $this->authorizeShare($request, $project);

        $data = $request->validate([
            'user' => ['required', 'integer'],
        ]);

        $target = User::query()->find($data['user']);

        // Target must be an existing workspace member (you share with members,
        // not arbitrary users). Reject owner/admin grants — they see all
        // anyway, so a grant row is meaningless noise.
        $targetRole = $target ? $target->roleIn($tenant) : null;
        if ($target === null || $targetRole === null) {
            throw ValidationException::withMessages([
                'user' => 'You can only share with members of this workspace.',
            ]);
        }
        if (in_array($targetRole, ['owner', 'admin'], true)) {
            throw ValidationException::withMessages([
                'user' => 'Owners and admins already have access to every project.',
            ]);
        }

        $grant = ProjectMember::query()->firstOrCreate([
            'project_id' => $project->id,
            'user_id' => $target->id,
        ]);

        // Only notify + audit on a fresh grant (firstOrCreate returns the
        // existing row on a re-share — don't double-notify).
        if ($grant->wasRecentlyCreated) {
            $actor = $request->user();
            // In-app bell row (synchronous) + queued email (Δ25, always-on).
            $target->notify(new ProjectShared($project, $actor));
            Mail::to($target->email)->queue(new ProjectAccessMail(
                recipientName: (string) $target->name,
                projectName: $project->name,
                actorName: $actor->name ?: $actor->email,
                kind: 'shared',
                openUrl: url("/editor/{$project->id}"),
            ));

            AuditLogger::record(
                'workspace.project.shared',
                $project,
                "Shared \"{$project->name}\" with {$target->email}",
                [
                    'project_id' => $project->id,
                    'tenant_id' => $tenant->id,
                    'target_user_id' => $target->id,
                ],
            );
        }

        return response()->json(['ok' => true]);
    }

    /**
     * DELETE /projects/{project}/members/{user}
     *
     * Revoke a member's visibility of the project. Idempotent (a missing grant
     * is a successful no-op revoke).
     */
    public function destroy(Request $request, Project $project, User $user): JsonResponse
    {
        $tenant = $this->authorizeShare($request, $project);

        $deleted = ProjectMember::query()
            ->where('project_id', $project->id)
            ->where('user_id', $user->id)
            ->delete();

        if ($deleted > 0) {
            $actor = $request->user();
            // In-app bell row (synchronous) + queued email (Δ25, always-on).
            $user->notify(new ProjectAccessRevoked($project, $actor));
            Mail::to($user->email)->queue(new ProjectAccessMail(
                recipientName: (string) $user->name,
                projectName: $project->name,
                actorName: $actor->name ?: $actor->email,
                kind: 'revoked',
            ));

            AuditLogger::record(
                'workspace.project.unshared',
                $project,
                "Revoked \"{$project->name}\" from {$user->email}",
                [
                    'project_id' => $project->id,
                    'tenant_id' => $tenant->id,
                    'target_user_id' => $user->id,
                ],
            );
        }

        return response()->json(['ok' => true]);
    }

    /**
     * Tenant-scope the project + gate the acting user as owner-or-admin via
     * WorkspacePolicy. Returns the project's tenant for downstream use.
     */
    private function authorizeShare(Request $request, Project $project): Tenant
    {
        $user = $request->user();

        // Tenant scope first — a project from another tenant 404s (don't leak
        // existence), mirroring the controller authorize seams.
        abort_unless($project->tenant_id === $user->tenant_id, 404);

        $tenant = $project->tenant;
        abort_unless($tenant !== null, 404);

        // Owner-or-admin only (WorkspacePolicy::shareProject / revoke). 403 —
        // capability denial, not a visibility leak (the member CAN see the
        // project list, they just can't manage grants).
        abort_unless($user->can('shareProject', $tenant), 403);

        return $tenant;
    }
}
