<?php

declare(strict_types=1);

namespace App\Models;

use App\Support\Concerns\DerivesWorkspaceIdFromTenant;
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\HasMany;

class FindingException extends Model
{
    use DerivesWorkspaceIdFromTenant;
    use HasFactory;

    public const string STATUS_PENDING = 'pending';

    public const string STATUS_ACTIVE = 'active';

    public const string STATUS_EXPIRING = 'expiring';

    public const string STATUS_EXPIRED = 'expired';

    public const string STATUS_REJECTED = 'rejected';

    public const string STATUS_REVOKED = 'revoked';

    public const string STATUS_SUPERSEDED = 'superseded';

    public const string VALIDITY_VALID = 'valid';

    public const string VALIDITY_EXPIRING = 'expiring';

    public const string VALIDITY_EXPIRED = 'expired';

    public const string VALIDITY_REVOKED = 'revoked';

    public const string VALIDITY_REJECTED = 'rejected';

    public const string VALIDITY_MISSING_SUPPORT = 'missing_support';

    protected $guarded = [];

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'requested_at' => 'datetime',
            'approved_at' => 'datetime',
            'rejected_at' => 'datetime',
            'revoked_at' => 'datetime',
            'effective_from' => 'datetime',
            'expires_at' => 'datetime',
            'review_due_at' => 'datetime',
            'evidence_summary' => 'array',
        ];
    }

    /**
     * @return BelongsTo<Workspace, $this>
     */
    public function workspace(): BelongsTo
    {
        return $this->belongsTo(Workspace::class);
    }

    /**
     * @return BelongsTo<ManagedEnvironment, $this>
     */
    public function tenant(): BelongsTo
    {
        return $this->belongsTo(ManagedEnvironment::class, 'managed_environment_id');
    }

    /**
     * @return BelongsTo<Finding, $this>
     */
    public function finding(): BelongsTo
    {
        return $this->belongsTo(Finding::class);
    }

    /**
     * @return BelongsTo<User, $this>
     */
    public function requester(): BelongsTo
    {
        return $this->belongsTo(User::class, 'requested_by_user_id');
    }

    /**
     * @return BelongsTo<User, $this>
     */
    public function owner(): BelongsTo
    {
        return $this->belongsTo(User::class, 'owner_user_id');
    }

    /**
     * @return BelongsTo<User, $this>
     */
    public function approver(): BelongsTo
    {
        return $this->belongsTo(User::class, 'approved_by_user_id');
    }

    /**
     * @return BelongsTo<FindingExceptionDecision, $this>
     */
    public function currentDecision(): BelongsTo
    {
        return $this->belongsTo(FindingExceptionDecision::class, 'current_decision_id');
    }

    /**
     * @return HasMany<FindingExceptionDecision, $this>
     */
    public function decisions(): HasMany
    {
        return $this->hasMany(FindingExceptionDecision::class)
            ->orderBy('decided_at')
            ->orderBy('id');
    }

    /**
     * @return HasMany<FindingExceptionEvidenceReference, $this>
     */
    public function evidenceReferences(): HasMany
    {
        return $this->hasMany(FindingExceptionEvidenceReference::class)
            ->orderBy('id');
    }

    /**
     * @param  Builder<self>  $query
     * @return Builder<self>
     */
    public function scopeForFinding(Builder $query, Finding $finding): Builder
    {
        return $query->where('finding_id', (int) $finding->getKey());
    }

    /**
     * @param  Builder<self>  $query
     * @return Builder<self>
     */
    public function scopePending(Builder $query): Builder
    {
        return $query->where('status', self::STATUS_PENDING);
    }

    /**
     * @param  Builder<self>  $query
     * @return Builder<self>
     */
    public function scopeCurrent(Builder $query): Builder
    {
        return $query->whereIn('status', [
            self::STATUS_PENDING,
            self::STATUS_ACTIVE,
            self::STATUS_EXPIRING,
        ]);
    }

    public function isPending(): bool
    {
        return (string) $this->status === self::STATUS_PENDING;
    }

    public function isActiveLike(): bool
    {
        return in_array((string) $this->status, [
            self::STATUS_ACTIVE,
            self::STATUS_EXPIRING,
        ], true);
    }

    public function hasPriorApproval(): bool
    {
        return $this->approved_at !== null
            && $this->effective_from !== null
            && is_numeric($this->approved_by_user_id);
    }

    public function hasValidGovernance(): bool
    {
        return in_array((string) $this->current_validity_state, [
            self::VALIDITY_VALID,
            self::VALIDITY_EXPIRING,
        ], true);
    }

    public function currentDecisionType(): ?string
    {
        $decision = $this->relationLoaded('currentDecision')
            ? $this->currentDecision
            : $this->currentDecision()->first();

        return $decision instanceof FindingExceptionDecision
            ? (string) $decision->decision_type
            : null;
    }

    public function isPendingRenewal(): bool
    {
        return $this->isPending()
            && $this->hasPriorApproval()
            && $this->currentDecisionType() === FindingExceptionDecision::TYPE_RENEWAL_REQUESTED;
    }

    public function requiresFreshDecisionForFinding(Finding $finding): bool
    {
        return ! $finding->isRiskAccepted()
            && ! $this->isPending()
            && $this->hasValidGovernance();
    }

    public function canBeRenewed(): bool
    {
        return in_array((string) $this->status, [
            self::STATUS_ACTIVE,
            self::STATUS_EXPIRING,
            self::STATUS_EXPIRED,
        ], true);
    }

    public function canBeRevoked(): bool
    {
        if ($this->isPendingRenewal()) {
            return true;
        }

        return in_array((string) $this->status, [
            self::STATUS_ACTIVE,
            self::STATUS_EXPIRING,
        ], true);
    }
}
