<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller;
use App\Models\Plan;
use App\Models\Project;
use App\Models\ProjectMember;
use App\Models\Subscription;
use App\Models\Tenant;
use App\Models\User;
use App\Models\WorkspaceInvitation;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;

class TenantsController extends Controller
{
    public function index(Request $request): Response
    {
        $q = trim($request->string('q')->toString());
        $planFilter = trim($request->string('plan')->toString());

        $query = Tenant::query()
            ->withCount(['users', 'projects'])
            ->orderBy('name');

        if ($q !== '') {
            $query->where('name', 'like', "%{$q}%");
        }

        if ($planFilter !== '' && $planFilter !== 'all') {
            $query->whereIn('id', $this->tenantIdsOnPlan($planFilter));
        }

        $paginator = $query->paginate(50)->withQueryString();

        $pageTenantIds = $paginator->getCollection()->pluck('id')->all();

        // Owner = pivot `tenant_user.role = 'owner'` (the billing anchor —
        // exactly one per workspace, Δ24/Δ6). Display owner + derived plan now
        // agree with the plan FILTER, which uses the same pivot owner.
        $ownerByTenant = $pageTenantIds === []
            ? collect()
            : User::query()
                ->join('tenant_user', 'tenant_user.user_id', '=', 'users.id')
                ->whereIn('tenant_user.tenant_id', $pageTenantIds)
                ->where('tenant_user.role', 'owner')
                ->orderBy('users.id')
                ->get(['users.id', 'tenant_user.tenant_id as pivot_tenant_id', 'users.email'])
                ->keyBy('pivot_tenant_id');

        // Legacy fallback for tenants with no pivot owner (pre-P1 rows whose
        // owner pivot wasn't backfilled): owner = lowest-id user on the tenant.
        // Mirrors `Tenant::activePlan()`'s "no owner → fall back" robustness so
        // the admin view never blanks out an ownerless legacy tenant.
        $missingOwnerTenantIds = array_values(array_diff($pageTenantIds, $ownerByTenant->keys()->all()));
        if ($missingOwnerTenantIds !== []) {
            User::query()
                ->whereIn('tenant_id', $missingOwnerTenantIds)
                ->orderBy('id')
                ->get(['id', 'tenant_id', 'email'])
                ->groupBy('tenant_id')
                ->each(function ($users, $tenantId) use ($ownerByTenant) {
                    $first = $users->first();
                    if ($first !== null) {
                        $ownerByTenant->put($tenantId, (object) [
                            'id' => $first->id,
                            'pivot_tenant_id' => $tenantId,
                            'email' => $first->email,
                        ]);
                    }
                });
        }

        // Active plan per tenant = the OWNER's latest live subscription
        // (`Tenant::activePlan()` semantics), via the resolved owner.
        $planByTenant = [];
        if ($ownerByTenant->isNotEmpty()) {
            $ownerIdToTenant = $ownerByTenant->mapWithKeys(
                fn ($row) => [$row->id => $row->pivot_tenant_id]
            )->all();
            Subscription::query()
                ->with('plan:id,key')
                ->whereIn('user_id', array_keys($ownerIdToTenant))
                ->whereIn('status', ['active', 'trialing', 'past_due'])
                ->orderByDesc('id')
                ->get(['id', 'user_id', 'plan_id'])
                ->each(function (Subscription $sub) use ($ownerIdToTenant, &$planByTenant) {
                    $tenantId = $ownerIdToTenant[$sub->user_id] ?? null;
                    if ($tenantId !== null && ! isset($planByTenant[$tenantId])) {
                        $planByTenant[$tenantId] = $sub->plan?->key ?? 'free';
                    }
                });
        }

        $tenants = $paginator->through(fn (Tenant $t) => [
            'id' => $t->id,
            'name' => $t->name,
            'users_count' => (int) $t->users_count,
            'projects_count' => (int) $t->projects_count,
            'owner_id' => $ownerByTenant->get($t->id)?->id,
            'owner_email' => $ownerByTenant->get($t->id)?->email,
            'plan_key' => $planByTenant[$t->id] ?? 'free',
            'created_at' => $t->created_at?->toIso8601String(),
        ]);

        return Inertia::render('Admin/Tenants/Index', [
            'tenants' => $tenants,
            'filters' => [
                'q' => $q,
                'plan' => $planFilter === '' ? 'all' : $planFilter,
            ],
            'plans' => Plan::query()
                ->orderBy('position')
                ->get(['key', 'name'])
                ->all(),
        ]);
    }

    /**
     * Tenant ids whose active plan is `$planKey`, derived from the PIVOT
     * OWNER's subscription (`Tenant::activePlan()` semantics — Δ6) so the
     * filter and the displayed plan can't disagree on multi-user tenants.
     * `free` = "the owner has no active subscription" (we don't write a
     * `free` Subscription row).
     *
     * @return array<int, int>
     */
    private function tenantIdsOnPlan(string $planKey): array
    {
        // Pivot owner per tenant (the billing anchor — exactly one).
        // Keyed user_id => tenant_id.
        $ownerTenant = User::query()
            ->join('tenant_user', 'tenant_user.user_id', '=', 'users.id')
            ->where('tenant_user.role', 'owner')
            ->pluck('tenant_user.tenant_id', 'users.id');

        // Legacy fallback (pre-P1 tenants with no pivot owner): owner =
        // lowest-id user on the tenant. Keeps the filter in lockstep with the
        // display derivation, which applies the same fallback.
        $tenantsWithPivotOwner = $ownerTenant->values()->unique()->all();
        $legacyOwners = User::query()
            ->whereNotNull('tenant_id')
            ->whereNotIn('tenant_id', $tenantsWithPivotOwner === [] ? [0] : $tenantsWithPivotOwner)
            ->orderBy('id')
            ->get(['id', 'tenant_id'])
            ->groupBy('tenant_id');
        foreach ($legacyOwners as $tenantId => $users) {
            $first = $users->first();
            if ($first !== null && ! $ownerTenant->has($first->id)) {
                $ownerTenant->put($first->id, (int) $tenantId);
            }
        }

        if ($ownerTenant->isEmpty()) {
            return [];
        }

        $activeSubs = Subscription::query()
            ->with('plan:id,key')
            ->whereIn('user_id', $ownerTenant->keys()->all())
            ->whereIn('status', ['active', 'trialing', 'past_due'])
            ->orderByDesc('id')
            ->get(['id', 'user_id', 'plan_id']);

        $tenantsWithSub = [];
        $tenantsByPlan = [];
        foreach ($activeSubs as $sub) {
            $tenantId = $ownerTenant->get($sub->user_id);
            if ($tenantId === null) {
                continue;
            }
            // First (latest-id) live sub per owner wins, matching activePlan().
            if (isset($tenantsWithSub[$tenantId])) {
                continue;
            }
            $tenantsWithSub[$tenantId] = true;
            $tenantsByPlan[$sub->plan?->key ?? 'free'][$tenantId] = $tenantId;
        }

        if ($planKey === 'free') {
            // Tenants whose owner has no active subscription are on free.
            return array_values(
                array_diff(array_unique($ownerTenant->values()->all()), array_keys($tenantsWithSub))
            );
        }

        return array_values($tenantsByPlan[$planKey] ?? []);
    }

    public function show(Request $request, Tenant $tenant): Response
    {
        // Membership = the pivot (the source of truth — Δ24). Each member
        // carries their per-workspace role. Pre-P1 legacy tenants with no
        // pivot rows fall back to the `users.tenant_id` pointer set below.
        $memberRows = $tenant->members()
            ->orderBy('users.id')
            ->get([
                'users.id',
                'users.name',
                'users.email',
                'users.is_admin',
                'users.disabled_at',
                'users.created_at',
            ]);

        // Legacy fallback: a pre-backfill tenant has no pivot members — show
        // every user pointed at it so the admin view never blanks out.
        if ($memberRows->isEmpty()) {
            $memberRows = User::query()
                ->where('tenant_id', $tenant->id)
                ->orderBy('id')
                ->get(['id', 'name', 'email', 'is_admin', 'disabled_at', 'created_at']);
        }

        $users = $memberRows
            ->map(fn (User $u) => [
                'id' => $u->id,
                'name' => $u->name,
                'email' => $u->email,
                // Pivot role for pivot members; lowest-id legacy fallback gets
                // 'owner' (it's the de-facto owner under the old model).
                'role' => $u->getRelationValue('pivot')?->role
                    ?? ($u->is($memberRows->first()) ? 'owner' : 'editor'),
                'is_admin' => (bool) $u->is_admin,
                'disabled' => $u->disabled_at !== null,
                'created_at' => $u->created_at?->toIso8601String(),
            ])
            ->values();

        // The billing-anchor owner (pivot role='owner'), else the lowest-id
        // member. The header View owner / Impersonate actions hang off it.
        $ownerRow = $tenant->owner()->orderBy('users.id')->first()
            ?? $memberRows->first();

        $memberIds = $memberRows->pluck('id')->all();

        $projectRows = Project::query()
            ->where('tenant_id', $tenant->id)
            ->withCount('screens')
            ->orderByDesc('updated_at')
            ->get(['id', 'name', 'updated_at']);

        // Per-project visibility grants (Δ23/Δ25/#4): which member can see
        // which project. Owner + admins see all (no grant rows) — surfaced as
        // an "all" flag on each project so the view doesn't imply they're
        // excluded. Editors/viewers appear only against projects granted to
        // them. One query for every project on the page.
        $projectIds = $projectRows->pluck('id')->all();
        $grantsByProject = [];
        if ($projectIds !== []) {
            $nameById = $memberRows->keyBy('id');
            ProjectMember::query()
                ->whereIn('project_id', $projectIds)
                ->whereIn('user_id', $memberIds)
                ->get(['project_id', 'user_id'])
                ->each(function (ProjectMember $g) use (&$grantsByProject, $nameById) {
                    $member = $nameById->get($g->user_id);
                    if ($member === null) {
                        return;
                    }
                    $grantsByProject[$g->project_id][] = [
                        'id' => $member->id,
                        'name' => $member->name,
                    ];
                });
        }

        // Owner + admins (implicit all-projects visibility) — named once so
        // the view can render "all members" without per-project grant rows.
        $allAccessMembers = $users
            ->whereIn('role', ['owner', 'admin'])
            ->map(fn ($u) => ['id' => $u['id'], 'name' => $u['name']])
            ->values();

        $projects = $projectRows
            ->map(fn (Project $p) => [
                'id' => $p->id,
                'name' => $p->name,
                'screens_count' => (int) $p->screens_count,
                'updated_at' => $p->updated_at?->toIso8601String(),
                // Explicit per-project grants (editors/viewers). Owner/admins
                // are implied by `all_access` so they aren't double-listed.
                'granted_members' => $grantsByProject[$p->id] ?? [],
            ])
            ->values();

        // Pending (un-accepted, un-expired) workspace invitations.
        $invitations = WorkspaceInvitation::query()
            ->where('tenant_id', $tenant->id)
            ->whereNull('accepted_at')
            ->where(function ($w) {
                $w->whereNull('expires_at')->orWhere('expires_at', '>', now());
            })
            ->orderBy('id')
            ->get(['id', 'email', 'role', 'expires_at'])
            ->map(fn (WorkspaceInvitation $inv) => [
                'id' => $inv->id,
                'email' => $inv->email,
                'role' => $inv->role,
                'expires_at' => $inv->expires_at?->toIso8601String(),
            ])
            ->values();

        // Active plan = the OWNER's latest live subscription (Tenant::activePlan
        // semantics — Δ6) so the KPI agrees with the index list. Fall back to
        // "any member's" sub only when there's no resolvable owner.
        $subUserIds = $ownerRow !== null ? [$ownerRow->id] : $memberIds;
        $activeSub = $subUserIds === []
            ? null
            : Subscription::query()
                ->with('plan:id,key,name')
                ->whereIn('user_id', $subUserIds)
                ->whereIn('status', ['active', 'trialing', 'past_due'])
                ->orderByDesc('id')
                ->first();

        return Inertia::render('Admin/Tenants/Show', [
            'tenant' => [
                'id' => $tenant->id,
                'name' => $tenant->name,
                'created_at' => $tenant->created_at?->toIso8601String(),
                'personal' => (bool) $tenant->personal,
                'users_count' => $users->count(),
                'projects_count' => $projects->count(),
            ],
            'owner' => $ownerRow ? [
                'id' => $ownerRow->id,
                'name' => $ownerRow->name,
                'email' => $ownerRow->email,
                'is_admin' => (bool) $ownerRow->is_admin,
                'disabled' => $ownerRow->disabled_at !== null,
            ] : null,
            'activeSubscription' => $activeSub ? [
                'id' => $activeSub->id,
                'plan_key' => $activeSub->plan?->key ?? 'free',
                'plan_name' => $activeSub->plan?->name,
                'status' => $activeSub->status,
                'period' => $activeSub->period,
                'current_period_ends_at' => $activeSub->current_period_ends_at?->toIso8601String(),
                'cancels_at_period_end' => (bool) $activeSub->cancels_at_period_end,
            ] : null,
            'users' => $users,
            'projects' => $projects,
            'invitations' => $invitations,
            'allAccessMembers' => $allAccessMembers,
        ]);
    }
}
