<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Log;
use Laravel\Sanctum\HasApiTokens;
use Laravolt\Avatar\Facade as Avatar;

class User extends Authenticatable implements MustVerifyEmail
{
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $guarded = [];

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

    /**
     * The attributes that should be cast.
     *
     * @var array<string, string>
     */
    protected $casts = [
        'email_verified_at' => 'datetime',
        'is_profile_private' => 'boolean',
        'two_factor_enabled' => 'boolean',
        'two_factor_secret' => 'encrypted',
        'two_factor_confirmed_at' => 'datetime',
        'admin_permissions' => 'array',
    ];

    /** Staff roles a user can be promoted to. */
    public const ROLES = ['user', 'support', 'moderator', 'admin'];

    /**
     * Admin panel sections used for per-role permissions. Keys are stored in
     * admin_permissions; values are the route-name prefixes they unlock.
     */
    public const ADMIN_SECTIONS = [
        'dashboard' => ['admin.home', 'admin.analytics', 'admin.activity', 'admin.referrals', 'admin.leaderboard', 'admin.ai', 'admin.notifications'],
        'users' => ['admin.users', 'admin.face-verifications'],
        'leads' => ['admin.leads', 'admin.logs'],
        'cashouts' => ['admin.withdrawals', 'admin.payments'],
        'offers' => ['admin.offerwalls', 'admin.campaigns', 'admin.provider-offers'],
        'tickets' => ['admin.tickets'],
        'bonuses' => ['admin.bonus', 'admin.bonus-history'],
        'exports' => ['admin.export'],
        'settings' => ['admin.settings'],
    ];

    /** Full admins pass everything; staff need the section granted. */
    public function canAccessSection(string $section): bool
    {
        if ($this->is_admin) {
            return true;
        }

        return in_array($this->role, ['support', 'moderator'], true)
            && in_array($section, $this->admin_permissions ?? [], true);
    }

    /** Whether the user may open the admin panel at all. */
    public function isStaff(): bool
    {
        return $this->is_admin
            || (in_array($this->role, ['support', 'moderator'], true) && !empty($this->admin_permissions));
    }

    /** Route name of the first admin page this staff member can open. */
    public function adminEntryRoute(): string
    {
        if ($this->is_admin) {
            return 'admin.home';
        }

        $entries = [
            'dashboard' => 'admin.home',
            'users' => 'admin.users',
            'leads' => 'admin.leads',
            'cashouts' => 'admin.withdrawals',
            'offers' => 'admin.provider-offers',
            'tickets' => 'admin.tickets',
            'bonuses' => 'admin.bonus',
            'exports' => 'admin.export.all',
            'settings' => 'admin.settings',
        ];

        foreach ($entries as $section => $route) {
            if ($this->canAccessSection($section)) {
                return $route;
            }
        }

        return 'admin.home';
    }

    /** Resolve an admin route name (admin.users.view, …) to its section key. */
    public static function sectionForRouteName(?string $routeName): ?string
    {
        if (!$routeName) {
            return null;
        }

        foreach (self::ADMIN_SECTIONS as $section => $prefixes) {
            foreach ($prefixes as $prefix) {
                if ($routeName === $prefix || str_starts_with($routeName, $prefix.'.')) {
                    return $section;
                }
            }
        }

        return null;
    }

    /**
     * Whether this account must pass a 2FA challenge at login. Respects the
     * site-wide toggle so admins can disable the whole feature at once.
     */
    public function twoFactorActive(): bool
    {
        return site_bool('two_factor_site_enabled', false)
            && $this->two_factor_enabled
            && $this->two_factor_confirmed_at !== null
            && in_array($this->two_factor_method, ['email', 'totp'], true);
    }

    /**
     * A broken/unconfigured mail server must never block registration or
     * login flows, so verification emails fail soft and are logged instead.
     */
    public function sendEmailVerificationNotification()
    {
        try {
            parent::sendEmailVerificationNotification();
        } catch (\Throwable $exception) {
            Log::warning('Could not send verification email: '.$exception->getMessage(), [
                'user_id' => $this->id,
            ]);
        }
    }

    public function name(): string
    {
        return $this->username;
    }

    public function avatar()
    {
        return $this->profile_photo_path ? \Storage::url($this->profile_photo_path) : Avatar::create($this->name())->toBase64();
    }

    public function displayNameForPublic(): string
    {
        // Privacy hides the member's offer history, not their public identity.
        return $this->username;
    }

    public function balanceDisplayMode(): string
    {
        return $this->balance_display === 'usd' ? 'usd' : 'coins';
    }

    public function referralCode(): string
    {
        if (!$this->referral_code) {
            $this->referral_code = strtoupper(substr(preg_replace('/[^A-Za-z0-9]/', '', $this->username), 0, 5))
                . $this->id
                . strtoupper(substr(md5($this->email . $this->id), 0, 3));
            $this->save();
        }

        return $this->referral_code;
    }

    public function usdBalance(): float
    {
        return coins_to_usd($this->current_points);
    }

    public function formattedBalance(?string $mode = null): string
    {
        $mode = $mode ?: $this->balanceDisplayMode();

        if ($mode === 'usd') {
            return '$' . number_format($this->usdBalance(), 2);
        }

        return number_format((float) $this->current_points, 2) . ' coins';
    }

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

    public function latestLead(): HasOne
    {
        return $this->hasOne(Lead::class)->where('credited_points', '>', 0)->latestOfMany('updated_at');
    }

    public function withdrawals(): HasMany
    {
        return $this->hasMany(Withdrawal::class)->orderBy('created_at', 'desc');
    }

    public function referredUsers(): HasMany
    {
        return $this->hasMany(User::class, 'referred_by');
    }

    public function referralEvents(): HasMany
    {
        return $this->hasMany(ReferralEvent::class, 'referrer_id');
    }

    public function notifications(): HasMany
    {
        return $this->hasMany(Notification::class)->orderBy('created_at', 'desc');
    }

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

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

    public function latestFaceVerification(): HasOne
    {
        return $this->hasOne(FaceVerification::class)->latestOfMany();
    }

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

    public function addPoints(int $points): void
    {
        $this->current_points += $points;
        $this->today_points += $points;
        $this->total_points += $points;
        $this->save();
    }
}
