<?php

namespace App\Models;

use App\Support\Tenants\TenantLifecycle;
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 ManagedEnvironment extends Model implements HasName
{
    use HasFactory;
    use SoftDeletes;

    protected static bool $skipTestWorkspaceProvisioning = false;

    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',
        'is_current' => 'boolean',
        'rbac_last_checked_at' => 'datetime',
        'removed_from_workspace_at' => 'datetime',
    ];

    public function getExternalIdAttribute(): ?string
    {
        return $this->attributes['slug'] ?? null;
    }

    public function setExternalIdAttribute(mixed $value): void
    {
        $this->attributes['slug'] = is_string($value) ? $value : null;
    }

    public function getStatusAttribute(): ?string
    {
        return $this->attributes['lifecycle_status'] ?? null;
    }

    public function setStatusAttribute(mixed $value): void
    {
        $this->attributes['lifecycle_status'] = is_string($value) ? $value : null;
    }

    public function getEnvironmentAttribute(): ?string
    {
        return $this->attributes['kind'] ?? null;
    }

    public function setEnvironmentAttribute(mixed $value): void
    {
        $this->attributes['kind'] = is_string($value) ? $value : null;
    }

    public function getManagedEnvironmentIdAttribute(): ?string
    {
        return $this->attributes['slug'] ?? null;
    }

    public function setManagedEnvironmentIdAttribute(mixed $value): void
    {
        $this->attributes['slug'] = is_string($value) ? $value : null;
    }

    public function getAppClientIdAttribute(): ?string
    {
        return $this->legacyProviderClientId();
    }

    public function setAppClientIdAttribute(mixed $value): void
    {
        $this->setLegacyMetadataValue('app_client_id', $value);
    }

    public function getAppClientSecretAttribute(): ?string
    {
        return $this->legacyMetadataValue('app_client_secret');
    }

    public function setAppClientSecretAttribute(mixed $value): void
    {
        $this->setLegacyMetadataValue('app_client_secret', $value);
    }

    public function getAppCertificateThumbprintAttribute(): ?string
    {
        return $this->legacyMetadataValue('app_certificate_thumbprint');
    }

    public function setAppCertificateThumbprintAttribute(mixed $value): void
    {
        $this->setLegacyMetadataValue('app_certificate_thumbprint', $value);
    }

    public function getAppNotesAttribute(): ?string
    {
        return $this->legacyMetadataValue('app_notes');
    }

    public function setAppNotesAttribute(mixed $value): void
    {
        $this->setLegacyMetadataValue('app_notes', $value);
    }

    public function getAppStatusAttribute(): ?string
    {
        $metadata = $this->metadata;

        return is_array($metadata) && is_string($metadata['app_status'] ?? null)
            ? $metadata['app_status']
            : null;
    }

    public function setAppStatusAttribute(mixed $value): void
    {
        $metadata = $this->metadata;
        $metadata = is_array($metadata) ? $metadata : [];

        if (is_string($value) && $value !== '') {
            $metadata['app_status'] = $value;
        } else {
            unset($metadata['app_status']);
        }

        $this->attributes['metadata'] = json_encode($metadata, JSON_THROW_ON_ERROR);
    }

    public function getDomainAttribute(): ?string
    {
        $metadata = $this->metadata;

        return is_array($metadata) && is_string($metadata['domain'] ?? null)
            ? $metadata['domain']
            : null;
    }

    public function setDomainAttribute(mixed $value): void
    {
        $metadata = $this->metadata;
        $metadata = is_array($metadata) ? $metadata : [];

        if (is_string($value) && $value !== '') {
            $metadata['domain'] = $value;
        } else {
            unset($metadata['domain']);
        }

        $this->attributes['metadata'] = json_encode($metadata, JSON_THROW_ON_ERROR);
    }

    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 (ManagedEnvironment $tenant) {
            if (empty($tenant->slug)) {
                $tenant->slug = Str::slug((string) $tenant->name);

                if ($tenant->slug === '') {
                    $tenant->slug = (string) Str::uuid();
                }
            }

            if (empty($tenant->kind)) {
                $tenant->kind = 'managed_environment';
            }

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

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

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

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

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

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

    public static function activeQuery(): Builder
    {
        return static::query()
            ->whereNull('deleted_at')
            ->whereNull('removed_from_workspace_at')
            ->where('lifecycle_status', TenantLifecycle::Active->value)
            ->whereHas('workspace', fn (Builder $query): Builder => $query
                ->whereNull('archived_at')
                ->whereNull('closed_at'));
    }

    public static function skipTestWorkspaceProvisioning(bool $skip = true): void
    {
        static::$skipTestWorkspaceProvisioning = $skip;
    }

    public function makeCurrent(): void
    {
        if (! $this->isSelectableAsContext()) {
            throw new RuntimeException('Only active managed environments 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('slug', $envTenantId);
                })
                ->first();

            if (! $tenant) {
                throw new RuntimeException('Configured INTUNE_TENANT_ID managed environment 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 managed environment selected.');
        }

        return $tenant;
    }

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

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

        $query = static::query();

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

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

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

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

    public function removedFromWorkspaceByUser(): BelongsTo
    {
        return $this->belongsTo(User::class, 'removed_from_workspace_by_user_id');
    }

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

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

        return ($this->display_name ?: $this->name)." ({$environment})";
    }

    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class, 'managed_environment_memberships')
            ->using(ManagedEnvironmentMembership::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 findingExceptions(): HasMany
    {
        return $this->hasMany(FindingException::class);
    }

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

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

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

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

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

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

    public function graphTenantId(): ?string
    {
        $connection = $this->providerConnections()
            ->where('provider', 'microsoft')
            ->where('is_default', true)
            ->first();

        return is_string($connection?->entra_tenant_id) && $connection->entra_tenant_id !== ''
            ? $connection->entra_tenant_id
            : null;
    }

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

    public function legacyProviderClientId(): ?string
    {
        $clientId = trim((string) $this->providerConnections()
            ->where('provider', 'microsoft')
            ->where('is_default', true)
            ->first()?->effectiveAppId());

        if ($clientId !== '') {
            return $clientId;
        }

        return $this->legacyMetadataValue('app_client_id');
    }

    public function hasLegacyProviderSecret(): bool
    {
        return (bool) $this->providerConnections()
            ->where('provider', 'microsoft')
            ->where('is_default', true)
            ->whereHas('credential')
            ->exists();
    }

    /**
     * @return array{client_id: ?string, has_secret: bool}
     */
    public function legacyProviderIdentity(): array
    {
        return [
            'client_id' => $this->legacyProviderClientId(),
            'has_secret' => $this->hasLegacyProviderSecret(),
        ];
    }

    private function legacyMetadataValue(string $key): ?string
    {
        $metadata = $this->metadata;

        return is_array($metadata) && is_string($metadata[$key] ?? null) && $metadata[$key] !== ''
            ? $metadata[$key]
            : null;
    }

    private function setLegacyMetadataValue(string $key, mixed $value): void
    {
        $metadata = $this->metadata;
        $metadata = is_array($metadata) ? $metadata : [];

        if (is_string($value) && $value !== '') {
            $metadata[$key] = $value;
        } else {
            unset($metadata[$key]);
        }

        $this->attributes['metadata'] = json_encode($metadata, JSON_THROW_ON_ERROR);
    }

    /**
     * @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(
            'ManagedEnvironment::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('slug', $tenant);
    }

    public function isActive(): bool
    {
        return $this->isSelectableAsContext();
    }

    public function lifecycle(): TenantLifecycle
    {
        return TenantLifecycle::fromTenant($this);
    }

    public function isDraft(): bool
    {
        return $this->lifecycle() === TenantLifecycle::Draft;
    }

    public function isOnboarding(): bool
    {
        return $this->lifecycle() === TenantLifecycle::Onboarding;
    }

    public function isArchived(): bool
    {
        return $this->lifecycle() === TenantLifecycle::Archived;
    }

    public function isSelectableAsContext(): bool
    {
        if ($this->trashed() || $this->isRemovedFromWorkspace() || ! $this->lifecycle()->canSelectAsContext()) {
            return false;
        }

        if ($this->workspace_id === null) {
            return false;
        }

        $workspace = $this->relationLoaded('workspace')
            ? $this->workspace
            : $this->workspace()->first(['id', 'archived_at', 'closed_at']);

        return $workspace instanceof Workspace && $workspace->isSelectableAsContext();
    }

    public function canResumeOnboarding(): bool
    {
        return ! $this->trashed() && ! $this->isRemovedFromWorkspace() && $this->lifecycle()->canResumeOnboarding();
    }

    public function isRemovedFromWorkspace(): bool
    {
        return $this->removed_from_workspace_at !== null;
    }

    public function workspaceRemovalReason(): ?string
    {
        $reason = trim((string) $this->removed_from_workspace_reason);

        return $reason === '' ? null : $reason;
    }
}
