<?php

namespace App\Models\Tenant;

use App\Models\Concerns\BelongsToTenant;
use Filament\Auth\MultiFactor\App\Contracts\HasAppAuthentication;
use Filament\Auth\MultiFactor\App\Contracts\HasAppAuthenticationRecovery;
use Filament\Auth\MultiFactor\Email\Contracts\HasEmailAuthentication;
use Filament\Models\Contracts\FilamentUser;
use Filament\Panel;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;

/**
 * Tenant-bound user. Belongs to exactly one Company.
 * UNIQUE(company_id, email) — same email can exist in different tenants
 * as completely separate accounts.
 */
class User extends Authenticatable implements FilamentUser, HasAppAuthentication, HasAppAuthenticationRecovery, HasEmailAuthentication
{
    use BelongsToTenant, HasApiTokens, HasFactory, HasRoles, Notifiable, SoftDeletes;

    /** Spatie Permission scope key matches the team_foreign_key (company_id). */
    protected $guard_name = 'web';

    protected $fillable = [
        'company_id', 'name', 'email', 'phone', 'avatar_url',
        'role', 'status', 'locale',
        'specialization', 'color', 'hourly_rate', 'internal_margin_pct',
        'email_verified_at', 'password', 'last_login_at',
        'email_authentication_at',
        'app_authentication_secret', 'app_authentication_recovery_codes',
        'invited_at', 'invited_by_id', 'accepted_at', 'invitation_token',
    ];

    protected $hidden = [
        'password', 'remember_token',
    ];

    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'last_login_at' => 'datetime',
            'email_authentication_at' => 'datetime',
            'invited_at' => 'datetime',
            'accepted_at' => 'datetime',
            'password' => 'hashed',
            'app_authentication_secret' => 'encrypted',
            'app_authentication_recovery_codes' => 'encrypted:array',
        ];
    }

    public function canAccessPanel(Panel $panel): bool
    {
        return $panel->getId() === 'tenant'
            && $this->status === 'active';
    }

    public function isAdmin(): bool
    {
        return $this->role === 'admin' || $this->role === 'owner' || $this->hasAnyRole(['admin', 'owner']);
    }

    public function isOwner(): bool
    {
        return $this->role === 'owner' || $this->hasRole('owner');
    }

    public function isAccountant(): bool
    {
        return $this->role === 'accountant' || $this->hasRole('accountant');
    }

    public function isMechanic(): bool
    {
        return in_array($this->role, ['mechanic', 'master'], true) || $this->hasAnyRole(['mechanic']);
    }

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

    public function invitedBy(): \Illuminate\Database\Eloquent\Relations\BelongsTo
    {
        return $this->belongsTo(self::class, 'invited_by_id');
    }

    public function company(): \Illuminate\Database\Eloquent\Relations\BelongsTo
    {
        return $this->belongsTo(\App\Models\Central\Company::class);
    }

    /**
     * Permission check honoring (in order):
     *   1. Active deny-override → false
     *   2. Active grant-override → true
     *   3. Admin/owner bypass → true
     *   4. Standard role-based check
     */
    public function canDo(string $permission): bool
    {
        $override = $this->activeOverrideFor($permission);
        if ($override) {
            if ($override->mode === 'deny') {
                $this->logDeniedIfSensitive($permission);
                return false;
            }
            if ($override->mode === 'grant') return true;
        }

        // Owner + admin bypass for permissions without explicit deny.
        if ($this->isAdmin()) return true;

        try {
            $allowed = $this->can($permission);
            if (! $allowed) $this->logDeniedIfSensitive($permission);
            return $allowed;
        } catch (\Throwable $e) {
            return false;
        }
    }

    private function activeOverrideFor(string $permissionSlug): ?UserPermissionOverride
    {
        return $this->permissionOverrides()
            ->whereHas('permission', fn ($q) => $q->where('name', $permissionSlug))
            ->where(fn ($q) => $q->whereNull('expires_at')->orWhere('expires_at', '>', now()))
            ->first();
    }

    /** Sensitive permissions whose deny we should record for audit. */
    private const AUDITED_DENIALS = [
        'admin.users.manage', 'admin.roles.manage', 'admin.settings.edit', 'admin.backup.download',
        'finance.delete_payment', 'finance.view_pl',
        'salaries.mark_paid', 'salaries.view_all',
        'work_orders.delete', 'work_orders.approve_discount_any',
    ];

    private function logDeniedIfSensitive(string $permission): void
    {
        if (! in_array($permission, self::AUDITED_DENIALS, true)) return;
        try {
            activity('permissions')
                ->causedBy($this)
                ->withProperties(['permission' => $permission])
                ->event('permission_denied')
                ->log("permission denied: $permission for user #{$this->id}");
        } catch (\Throwable $e) {
            // activity-log may be misconfigured in some contexts — never let auth fail because of it.
        }
    }

    /** Has 2FA app authentication enabled (Filament native). */
    public function hasTwoFactorEnabled(): bool
    {
        return $this->app_authentication_secret !== null;
    }

    /** Pending invitation (sent but not yet accepted). */
    public function isPendingInvitation(): bool
    {
        return $this->invited_at !== null && $this->accepted_at === null;
    }

    /**
     * Create + send an invitation: generates a random token, marks invited_at,
     * and queues the email with the signed accept link. Idempotent — calling
     * again regenerates the token (useful for "resend invitation").
     */
    public function sendInvitation(?User $invitedBy = null): string
    {
        $token = bin2hex(random_bytes(32));   // 64 chars
        $this->forceFill([
            'invitation_token' => hash('sha256', $token),
            'invited_at' => now(),
            'invited_by_id' => $invitedBy?->id ?? auth()->id(),
            'accepted_at' => null,
            'status' => 'inactive',  // can't login until accepted
        ])->saveQuietly();

        \Illuminate\Support\Facades\Mail::to($this->email)
            ->queue(new \App\Mail\UserInvitationMail($this, $token));

        return $token;   // returned mainly for tests / API
    }

    public static function findByInvitationToken(string $rawToken): ?self
    {
        return self::where('invitation_token', hash('sha256', $rawToken))->first();
    }

    public function acceptInvitation(string $password): void
    {
        $this->forceFill([
            'password' => $password,    // hashed cast handles it
            'invitation_token' => null,
            'accepted_at' => now(),
            'status' => 'active',
            'email_verified_at' => now(),
        ])->save();
    }

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

    public function toggleEmailAuthentication(bool $condition): void
    {
        $this->forceFill([
            'email_authentication_at' => $condition ? now() : null,
        ])->saveQuietly();
    }

    public function getAppAuthenticationSecret(): ?string
    {
        return $this->app_authentication_secret;
    }

    public function saveAppAuthenticationSecret(?string $secret): void
    {
        $this->forceFill(['app_authentication_secret' => $secret])->saveQuietly();
    }

    public function getAppAuthenticationHolderName(): string
    {
        return $this->email;
    }

    public function getAppAuthenticationRecoveryCodes(): ?array
    {
        return $this->app_authentication_recovery_codes;
    }

    public function saveAppAuthenticationRecoveryCodes(?array $codes): void
    {
        $this->forceFill(['app_authentication_recovery_codes' => $codes])->saveQuietly();
    }
}
