<?php

namespace App\Models;

use Filament\Facades\Filament;
use Filament\Models\Contracts\HasName;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasManyThrough;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use RuntimeException;

class Tenant extends Model implements HasName
{
    use HasFactory;
    use SoftDeletes;

    public const STATUS_DRAFT = 'draft';

    public const STATUS_ONBOARDING = 'onboarding';

    public const STATUS_ACTIVE = 'active';

    public const STATUS_ARCHIVED = 'archived';

    protected $guarded = [];

    protected $casts = [
        'metadata' => 'array',
        'app_client_secret' => 'encrypted',
        'is_current' => 'boolean',
        'rbac_last_checked_at' => 'datetime',
        'rbac_last_setup_at' => 'datetime',
        'rbac_canary_results' => 'array',
        'rbac_last_warnings' => 'array',
    ];

    public function getRbacCanaryResultsAttribute($value): array
    {
        if (is_string($value)) {
            $decoded = json_decode($value, true);

            return is_array($decoded) ? $decoded : [];
        }

        return $value ?? [];
    }

    public function getRbacLastWarningsAttribute($value): array
    {
        if (is_string($value)) {
            $decoded = json_decode($value, true);

            return is_array($decoded) ? $decoded : [];
        }

        $warnings = $value ?? [];

        if ($this->rbac_scope_mode === 'scope_group' || filled($this->rbac_scope_id)) {
            $warnings[] = 'scope_limited';
        }

        return $warnings;
    }

    protected static function booted(): void
    {
        static::creating(function (Tenant $tenant) {
            if (empty($tenant->external_id)) {
                $tenant->external_id = $tenant->tenant_id ?? (string) Str::uuid();
            }

            if (empty($tenant->status)) {
                $tenant->status = self::STATUS_ACTIVE;
            }

            if ($tenant->workspace_id === null && app()->runningUnitTests()) {
                $workspace = Workspace::query()->create([
                    'name' => 'Test Workspace',
                    'slug' => 'test-'.Str::lower(Str::random(10)),
                ]);

                $tenant->workspace_id = (int) $workspace->getKey();
            }
        });

        static::saving(function (Tenant $tenant) {
            if (! empty($tenant->tenant_id)) {
                $tenant->external_id = $tenant->tenant_id;
            }
        });

        static::deleting(function (Tenant $tenant) {
            if ($tenant->isForceDeleting()) {
                return;
            }

            $tenant->status = self::STATUS_ARCHIVED;
            $tenant->saveQuietly();
        });

        static::restored(function (Tenant $tenant) {
            $tenant->forceFill(['status' => self::STATUS_ACTIVE])->saveQuietly();
        });
    }

    public static function activeQuery(): Builder
    {
        return static::query()
            ->whereNull('deleted_at')
            ->where('status', self::STATUS_ACTIVE);
    }

    public function makeCurrent(): void
    {
        if ($this->trashed() || $this->status !== self::STATUS_ACTIVE) {
            throw new RuntimeException('Only active tenants can be made current.');
        }

        DB::transaction(function () {
            static::activeQuery()->update(['is_current' => false]);

            static::query()
                ->whereKey($this->getKey())
                ->update(['is_current' => true]);
        });

        $this->forceFill(['is_current' => true]);
    }

    public static function current(): ?self
    {
        $filamentTenant = Filament::getTenant();

        if ($filamentTenant instanceof self) {
            return $filamentTenant;
        }

        $envTenantId = getenv('INTUNE_TENANT_ID') ?: null;

        if ($envTenantId) {
            $tenant = static::activeQuery()
                ->where(function (Builder $query) use ($envTenantId) {
                    $query->where('tenant_id', $envTenantId)
                        ->orWhere('external_id', $envTenantId);
                })
                ->first();

            if (! $tenant) {
                throw new RuntimeException('Configured INTUNE_TENANT_ID tenant is missing or inactive.');
            }

            return $tenant;
        }

        $tenant = static::activeQuery()
            ->where('is_current', true)
            ->first();

        return $tenant;
    }

    public static function currentOrFail(): self
    {
        $tenant = static::current();

        if (! $tenant) {
            throw new RuntimeException('No current tenant selected.');
        }

        return $tenant;
    }

    public function getRouteKeyName(): string
    {
        return 'external_id';
    }

    public function resolveRouteBinding($value, $field = null): ?Model
    {
        $field ??= $this->getRouteKeyName();

        $query = static::query();

        if ($field === 'external_id') {
            $query = $query->withTrashed();
        }

        return $query->where($field, $value)->first();
    }

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

    public function workspace(): BelongsTo
    {
        return $this->belongsTo(Workspace::class);
    }

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

    public function getFilamentName(): string
    {
        $environment = strtoupper((string) ($this->environment ?? 'other'));

        return "{$this->name} ({$environment})";
    }

    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class, 'tenant_memberships')
            ->using(TenantMembership::class)
            ->withPivot(['id', 'role', 'source', 'source_ref', 'created_by_user_id'])
            ->withTimestamps();
    }

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

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

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

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

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

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

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

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

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

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

    public function providerCredentials(): HasManyThrough
    {
        return $this->hasManyThrough(ProviderCredential::class, ProviderConnection::class, 'tenant_id', 'provider_connection_id');
    }

    public function graphTenantId(): ?string
    {
        return $this->tenant_id ?? $this->external_id;
    }

    /**
     * @deprecated Runtime provider calls must resolve ProviderConnection + ProviderGateway.
     *
     * @return array{tenant:?string,client_id:?string,client_secret:?string}
     */
    public function graphOptions(): array
    {
        throw new \BadMethodCallException(
            'Tenant::graphOptions() has been removed. Resolve a ProviderConnection via ProviderConnectionResolver and build options via ProviderGateway (or use MicrosoftGraphOptionsResolver).'
        );
    }

    public function scopeForTenant(Builder $query, self|int|string $tenant): Builder
    {
        if ($tenant instanceof self) {
            return $query->whereKey($tenant->getKey());
        }

        if (is_int($tenant) || ctype_digit((string) $tenant)) {
            return $query->whereKey($tenant);
        }

        return $query
            ->where('tenant_id', $tenant)
            ->orWhere('external_id', $tenant);
    }

    public function isActive(): bool
    {
        return ! $this->trashed() && ($this->status ?? 'active') === 'active';
    }
}
