<?php

namespace App\Models;

use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOneThrough;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Str;
use Laravel\Sanctum\HasApiTokens;
use Spatie\Permission\Traits\HasRoles;

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

    /**
     * Send the email verification notification.
     *
     * Two distinct policies, chosen by the admin via
     * Admin → Settings → Auth → "Require email verification":
     *
     * ─ require_email_verification = TRUE  (enforced — anti-spam mode) ────
     *   - DO NOT auto-mark verified. The user must click the email link
     *     before EnsureEmailIsVerified lets them past /email/verify.
     *   - Still defer the actual SMTP send via app()->terminating() so a
     *     slow mail host can't cause a 504 on the register POST. The user
     *     lands on /email/verify, sees "we sent you a link", and the
     *     email arrives a moment later. Worst case (SMTP totally dead)
     *     they hit "Resend verification email" from that page.
     *
     * ─ require_email_verification = FALSE  (friction-free / demo mode) ──
     *   - Auto-mark verified immediately so the user proceeds straight to
     *     onboarding without seeing the verify-email page at all.
     *   - The Fortify register flow still fires the notification, but it
     *     becomes a "welcome / for your records" email rather than a
     *     gate. Same deferred-send + 10s socket timeout safeguards.
     *
     * Either way the send is deferred. The Fortify register flow fires
     * `Registered`, Laravel's default `SendEmailVerificationNotification`
     * listener calls this method synchronously between CreateNewUser
     * ending and RegisterResponse starting — if we let SMTP block the
     * request, a slow mail host hangs the register POST for 60+ seconds
     * → Cloudflare 504.
     */
    public function sendEmailVerificationNotification(): void
    {
        // Anti-spam mode? When the toggle is OFF (or absent), keep the
        // friction-free behavior so existing installs don't suddenly start
        // gating users behind email verification without the admin
        // opting in. Default 'true' here matches the seeded default; the
        // 'false' branch is what existing installs without the row hit.
        $requireVerification = \App\Models\SystemSetting::get(
            'require_email_verification',
            'true',
        ) === 'true';

        // FRICTION-FREE MODE — mark verified now, send email as
        // informational. Same behavior the platform shipped with.
        if (!$requireVerification) {
            if (!$this->hasVerifiedEmail()) {
                $this->markEmailAsVerified();
            }
        }
        // ENFORCED MODE — skip the auto-mark. User stays unverified
        // until they click the link, and EnsureEmailIsVerified will
        // redirect them to /email/verify on the next protected page.

        // Skip even attempting to send when mail is obviously not
        // configured for real delivery. In ENFORCED mode this leaves
        // the user permanently unverified — that's correct: an admin
        // who enables required verification without configuring SMTP
        // has a setup error, and silently auto-verifying around it
        // would defeat the toggle.
        $mailer = config('mail.default');
        $smtpHost = config('mail.mailers.smtp.host');
        if (in_array($mailer, ['log', 'array', 'null', null], true)
            || empty($smtpHost)
            || $smtpHost === 'mailpit'
            || $smtpHost === '127.0.0.1') {
            return;
        }

        // Defer the real SMTP send to after the response is flushed.
        $self = $this;
        app()->terminating(function () use ($self) {
            $previousTimeout = ini_get('default_socket_timeout');
            @ini_set('default_socket_timeout', '10');
            try {
                // Call Authenticatable's parent implementation explicitly
                // via the Notifiable trait's notify() so we bypass this
                // override (which would recurse). Sends the standard
                // Laravel VerifyEmail notification.
                $self->notify(new \Illuminate\Auth\Notifications\VerifyEmail());
                \Illuminate\Support\Facades\Log::info('[RegFlow] verify-email sent post-response user_id=' . $self->id);
            } catch (\Throwable $e) {
                \Illuminate\Support\Facades\Log::warning('[RegFlow] verify-email failed post-response user_id=' . $self->id . ' err=' . substr($e->getMessage(), 0, 200));
            } finally {
                @ini_set('default_socket_timeout', (string) $previousTimeout);
            }
        });
    }

    protected $fillable = [
        'name',
        'email',
        'password',
        'phone',
        'avatar_path',
        'timezone',
        'locale',
        'language',
        'currency_code',
        // Customer billing snapshot used to pre-fill checkout + invoice PDFs.
        // The payments row keeps its own frozen copy per transaction; these
        // are the "last entered" values so repeat customers don't retype.
        'billing_company',
        'billing_tax_id',
        'billing_vat_number',
        'billing_address',
        'billing_email',
        'status',
        'referral_code',
        'referred_by',
        'active_workspace_id',
        'two_factor_enabled',
        'two_factor_secret',
        'two_factor_recovery_codes',
        'two_factor_method',
        'notification_preferences',
        // Seconds the post-send toast stays visible before the email is
        // actually dispatched. User-configurable from Settings → Profile;
        // valid values are 5/10/20/30. Default 10 matches pre-migration
        // hard-coded behaviour.
        'undo_send_delay_seconds',
    ];

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

    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
            'is_admin' => 'boolean',
            'two_factor_enabled' => 'boolean',
            'two_factor_confirmed_at' => 'datetime',
            'force_password_reset' => 'boolean',
            'suspended_at' => 'datetime',
            'deletion_requested_at' => 'datetime',
            'notification_preferences' => 'array',
            'two_factor_secret' => 'encrypted',
            'two_factor_recovery_codes' => 'encrypted',
        ];
    }

    /**
     * Determine if the user has fully confirmed 2FA (secret set + verified with a valid code).
     */
    public function hasTwoFactorEnabled(): bool
    {
        return $this->two_factor_enabled
            && $this->two_factor_secret
            && $this->two_factor_confirmed_at !== null;
    }

    protected static function booted(): void
    {
        static::creating(function (User $user) {
            $user->uuid = $user->uuid ?? Str::uuid();
            $user->referral_code = $user->referral_code ?? strtoupper(Str::random(8));
        });
    }

    // Relationships

    public function activeWorkspace(): BelongsTo
    {
        return $this->belongsTo(Workspace::class, 'active_workspace_id');
    }

    public function workspaces(): BelongsToMany
    {
        return $this->belongsToMany(Workspace::class, 'workspace_members')
            ->withPivot('role', 'status', 'available_for_assignment')
            ->withTimestamps();
    }

    public function ownedWorkspaces(): BelongsToMany
    {
        return $this->belongsToMany(Workspace::class, 'workspace_members')
            ->withPivot('role')
            ->wherePivot('role', 'owner');
    }

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

    /**
     * Get the user's current plan through their active workspace's subscription.
     *
     * NOTE: We use orderByDesc instead of latestOfMany because Eloquent's
     * latestOfMany() generates a sub-query with a dot-aliased aggregate
     * column (`subscriptions.id_aggregate`) which MariaDB rejects with a
     * 1064 syntax error. Plain orderByDesc on the through table works on
     * both MySQL and MariaDB and HasOneThrough auto-limits to one row.
     */
    public function currentPlan(): HasOneThrough
    {
        return $this->hasOneThrough(
            Plan::class,
            Subscription::class,
            'workspace_id',       // FK on subscriptions table
            'id',                 // FK on plans table
            'active_workspace_id', // LK on users table
            'plan_id'             // LK on subscriptions table
        )->where('subscriptions.status', 'active')
         ->orderByDesc('subscriptions.id');
    }

    // Helpers

    public function getFirstNameAttribute(): string
    {
        return explode(' ', $this->name)[0];
    }

    public function getLastNameAttribute(): ?string
    {
        $parts = preg_split('/\s+/', trim((string) $this->name)) ?: [];
        if (count($parts) < 2) {
            return null;
        }

        array_shift($parts);

        return trim(implode(' ', $parts)) ?: null;
    }

    public function getInitialsAttribute(): string
    {
        $parts = explode(' ', $this->name);
        $initials = strtoupper(substr($parts[0], 0, 1));
        if (count($parts) > 1) {
            $initials .= strtoupper(substr(end($parts), 0, 1));
        }
        return $initials;
    }

    public function isAdmin(): bool
    {
        return $this->is_admin === true;
    }

    public function isSuperAdmin(): bool
    {
        return $this->is_admin && $this->admin_role === 'super_admin';
    }

    public function isSupport(): bool
    {
        return $this->is_admin && $this->admin_role === 'support';
    }

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

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

    public function workspaceRole(?int $workspaceId = null): ?string
    {
        $workspaceId = $workspaceId ?? $this->active_workspace_id;
        if (!$workspaceId) return null;

        return $this->workspaces()
            ->where('workspaces.id', $workspaceId)
            ->first()?->pivot?->role;
    }

    public function isWorkspaceOwner(?int $workspaceId = null): bool
    {
        return $this->workspaceRole($workspaceId) === 'owner';
    }

    public function isWorkspaceAdmin(?int $workspaceId = null): bool
    {
        return in_array($this->workspaceRole($workspaceId), ['owner', 'admin']);
    }
}
