<?php

namespace App\Models;

use App\Services\Auth\CapabilityResolver;
use App\Services\Auth\ManagedEnvironmentAccessScopeResolver;
use App\Services\Tenants\TenantOperabilityService;
use App\Support\Auth\Capabilities;
use App\Support\Workspaces\WorkspaceContext;
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasDefaultTenant;
use Filament\Models\Contracts\HasTenants;
use Filament\Panel;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Schema;

class User extends Authenticatable implements FilamentUser, HasDefaultTenant, HasTenants
{
    /** @use HasFactory<\Database\Factories\UserFactory> */
    use HasFactory, Notifiable;

    use SoftDeletes;

    /**
     * The attributes that are mass assignable.
     *
     * @var list<string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
        'entra_tenant_id',
        'entra_object_id',
        'preferred_locale',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var list<string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }

    public function canAccessPanel(Panel $panel): bool
    {
        return $panel->getId() === 'admin';
    }

    public function tenants(): BelongsToMany
    {
        return $this->managedEnvironments();
    }

    public function managedEnvironments(): BelongsToMany
    {
        return $this->belongsToMany(ManagedEnvironment::class, 'managed_environment_memberships')
            ->using(ManagedEnvironmentMembership::class)
            ->withPivot(['id', 'role', 'source', 'source_ref', 'created_by_user_id'])
            ->withTimestamps();
    }

    public function workspaces(): BelongsToMany
    {
        return $this->belongsToMany(Workspace::class, 'workspace_memberships')
            ->using(WorkspaceMembership::class)
            ->withPivot(['id', 'role'])
            ->withTimestamps();
    }

    public function tenantMemberships(): HasMany
    {
        return $this->managedEnvironmentMemberships();
    }

    public function managedEnvironmentMemberships(): HasMany
    {
        return $this->hasMany(ManagedEnvironmentMembership::class);
    }

    public function workspaceMemberships(): HasMany
    {
        return $this->hasMany(WorkspaceMembership::class);
    }

    public function tenantPreferences(): HasMany
    {
        return $this->hasMany(UserTenantPreference::class);
    }

    private function tenantPreferencesTableExists(): bool
    {
        static $exists;

        return $exists ??= Schema::hasTable('user_managed_environment_preferences');
    }

    public function tenantRoleValue(ManagedEnvironment $tenant): ?string
    {
        /** @var CapabilityResolver $resolver */
        $resolver = app(CapabilityResolver::class);

        return $resolver->getRole($this, $tenant)?->value;
    }

    public function allowsTenantSync(ManagedEnvironment $tenant): bool
    {
        return Gate::forUser($this)->allows(Capabilities::TENANT_SYNC, $tenant);
    }

    public function canAccessTenant(Model $tenant): bool
    {
        if (! $tenant instanceof ManagedEnvironment) {
            return false;
        }

        /** @var CapabilityResolver $resolver */
        $resolver = app(CapabilityResolver::class);

        return $resolver->isMember($this, $tenant);
    }

    public function accessibleManagedEnvironmentsQuery(int $workspaceId): Builder
    {
        $query = ManagedEnvironment::query()
            ->where('managed_environments.workspace_id', $workspaceId);

        app(ManagedEnvironmentAccessScopeResolver::class)->applyWorkspaceScopeToQuery(
            query: $query,
            user: $this,
            workspaceId: $workspaceId,
            qualifiedEnvironmentColumn: 'managed_environments.id',
        );

        return $query;
    }

    public function getTenants(Panel $panel): array|Collection
    {
        $workspaceId = app(WorkspaceContext::class)->currentWorkspaceId();

        if (! is_int($workspaceId)) {
            return collect();
        }

        $operability = app(TenantOperabilityService::class);
        /** @var CapabilityResolver $resolver */
        $resolver = app(CapabilityResolver::class);

        $tenants = $this->accessibleManagedEnvironmentsQuery($workspaceId)
            ->orderBy('name')
            ->get();

        $resolver->primeMemberships($this, $tenants->modelKeys());

        return $operability->filterSelectable($tenants
            ->filter(fn (ManagedEnvironment $tenant): bool => $resolver->isMember($this, $tenant))
            ->values());
    }

    public function getDefaultTenant(Panel $panel): ?Model
    {
        $workspaceContext = app(WorkspaceContext::class);
        $workspaceId = $workspaceContext->currentWorkspaceId();

        if (! is_int($workspaceId)) {
            return null;
        }

        $operability = app(TenantOperabilityService::class);

        $rememberedEnvironment = $workspaceContext->rememberedEnvironment(request());

        if ($rememberedEnvironment instanceof ManagedEnvironment && $this->canAccessTenant($rememberedEnvironment)) {
            return $rememberedEnvironment;
        }

        $tenantId = null;

        if ($this->tenantPreferencesTableExists()) {
            $tenantId = $this->tenantPreferences()
                ->whereNotNull('last_used_at')
                ->orderByDesc('last_used_at')
                ->value('managed_environment_id');
        }

        if ($tenantId !== null) {
            $tenant = ManagedEnvironment::query()
                ->where('workspace_id', $workspaceId)
                ->whereKey($tenantId)
                ->first();

            if ($tenant instanceof ManagedEnvironment && $this->canAccessTenant($tenant) && $operability->canSelectAsContext($tenant)) {
                return $tenant;
            }
        }

        return $this->getTenants($panel)
            ->first(fn (ManagedEnvironment $tenant): bool => $operability->canSelectAsContext($tenant));
    }
}
