<?php

namespace App\Models;

use App\Enums\UserPlan;
use App\Observers\UserObserver;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Attributes\ObservedBy;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Cashier\Billable;
use Laravel\Fortify\TwoFactorAuthenticatable;
use Laravel\Sanctum\HasApiTokens;

#[ObservedBy([UserObserver::class])]
class User extends Authenticatable implements MustVerifyEmail
{
    use Billable;
    use HasApiTokens;

    /** @use HasFactory<\Database\Factories\UserFactory> */
    use HasFactory;

    use Notifiable;
    use TwoFactorAuthenticatable;

    /**
     * The attributes that are mass assignable.
     *
     * @var list<string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
        'user_plan',
        'user_accredited',
        'user_first_accredited',
        'assert_mode',
        'novice_training_complete',
        'intermediate_training_complete',
        'difficult_training_complete',
        'expert_training_complete',
    ];

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

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
            'user_plan' => UserPlan::class,
            'assert_mode' => 'boolean',
            'novice_training_complete' => 'boolean',
            'intermediate_training_complete' => 'boolean',
            'difficult_training_complete' => 'boolean',
            'expert_training_complete' => 'boolean',
        ];
    }

    protected function userAccredited(): Attribute
    {
        return Attribute::make(
            get: fn ($value) => $value ? \Carbon\Carbon::parse($value)->toIso8601String() : null,
        );
    }

    protected function userFirstAccredited(): Attribute
    {
        return Attribute::make(
            get: fn ($value) => $value ? \Carbon\Carbon::parse($value)->toIso8601String() : null,
        );
    }

    public function profile(): HasOne
    {
        return $this->hasOne(UserProfile::class);
    }

    /**
     * Determine if the customer has a Stripe customer ID.
     *
     * Cashier only checks for null, so a blank `stripe_id` reads as "this user already
     * has a Stripe customer" and every call built on it — checkout, the billing portal,
     * the webhook lookup — ends up asking Stripe for the customer with the empty id,
     * which fails with "The resource ID cannot be null or whitespace" (a 500, not a
     * handled error). Treat blank as absent so a new customer is created instead.
     */
    public function hasStripeId(): bool
    {
        return filled($this->stripe_id);
    }

    /**
     * Determine if the user is on the Pro plan.
     */
    public function isPro(): bool
    {
        return $this->user_plan === UserPlan::PRO;
    }

    /**
     * The route a user lands on once authenticated.
     *
     * Free users are sent to the subscription page rather than the dashboard: the
     * dashboard is of no use to them until they subscribe.
     */
    public function homeRoute(): string
    {
        return $this->isPro() ? 'dashboard' : 'subscription.edit';
    }
}
