<?php

namespace App\Models;

// use Illuminate\Contracts\Auth\MustVerifyEmail;
use App\Enums\Role;
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 Laravel\Sanctum\HasApiTokens;

class User extends Authenticatable
{
    /** @use HasFactory<\Database\Factories\UserFactory> */
    use HasApiTokens, HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var list<string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
        'role',
        'features',
        'is_member',
        'membership_started_at',
        'membership_expires_at',
        'coin_balance',
    ];

    /**
     * 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',
            'role' => Role::class,
            'features' => 'array',
            'is_member' => 'boolean',
            'membership_started_at' => 'datetime',
            'membership_expires_at' => 'datetime',
            'coin_balance' => 'integer',
        ];
    }

    /**
     * Check if user has a specific role.
     */
    public function hasRole(Role $role): bool
    {
        return $this->role === $role;
    }

    /**
     * Check if user is an admin.
     */
    public function isAdmin(): bool
    {
        return $this->hasRole(Role::Admin);
    }

    /**
     * Check if user is a partner.
     */
    public function isPartner(): bool
    {
        return $this->hasRole(Role::Partner);
    }

    /**
     * Check if user is a regular user.
     */
    public function isUser(): bool
    {
        return $this->hasRole(Role::User);
    }

    /**
     * Check if user has active membership.
     */
    public function hasActiveMembership(): bool
    {
        if (! $this->is_member) {
            return false;
        }

        if (! $this->membership_expires_at) {
            return false;
        }

        return $this->membership_expires_at->isFuture();
    }

    /**
     * Check if user membership is expired.
     */
    public function isMembershipExpired(): bool
    {
        if (! $this->is_member) {
            return true;
        }

        if (! $this->membership_expires_at) {
            return true;
        }

        return $this->membership_expires_at->isPast();
    }

    /**
     * Get remaining membership days.
     */
    public function getRemainingMembershipDays(): ?int
    {
        if (! $this->hasActiveMembership()) {
            return null;
        }

        return now()->diffInDays($this->membership_expires_at, false);
    }

    /**
     * Get the shows that the user has access to.
     */
    public function shows()
    {
        return $this->belongsToMany(Show::class)->withTimestamps()->withPivot('purchased_at');
    }

    /**
     * Check if user has access to a specific show.
     * User has access if:
     * 1. They have active membership, OR
     * 2. They have purchased the show
     */
    public function hasAccessToShow(Show|int|string $show): bool
    {
        // If user has active membership, they have access to all shows
        if ($this->hasActiveMembership()) {
            return true;
        }

        // Otherwise, check if they purchased the show
        if ($show instanceof Show) {
            return $this->shows()->where('shows.id', $show->id)->exists();
        }

        if (is_numeric($show)) {
            return $this->shows()->where('shows.id', $show)->exists();
        }

        // If string, assume it's showId
        return $this->shows()->where('shows.showId', $show)->exists();
    }

    /**
     * Check if user can purchase a show.
     * User cannot purchase if they have active membership (they already have access to all shows).
     */
    public function canPurchaseShow(Show|int|string $show): bool
    {
        // If user has active membership, they cannot purchase (already have access)
        if ($this->hasActiveMembership()) {
            return false;
        }

        // Otherwise, check if they already have access
        return ! $this->hasAccessToShow($show);
    }

    /**
     * Assign a show to the user (purchase/access).
     */
    public function assignShow(Show|int $show): void
    {
        $showId = $show instanceof Show ? $show->id : $show;

        if (! $this->hasAccessToShow($showId)) {
            $this->shows()->attach($showId, [
                'purchased_at' => now(),
            ]);
        }
    }

    /**
     * Remove show access from the user.
     */
    public function removeShow(Show|int $show): void
    {
        $showId = $show instanceof Show ? $show->id : $show;
        $this->shows()->detach($showId);
    }

    /**
     * Get the partner shows (only for partners).
     */
    public function partnerShows(): HasMany
    {
        return $this->hasMany(PartnerShow::class, 'partner_id');
    }

    /**
     * Get the partner settings (only for partners).
     */
    public function partnerSettings(): HasOne
    {
        return $this->hasOne(PartnerSetting::class, 'partner_id');
    }

    /**
     * Check if user has a specific feature.
     */
    public function hasFeature(string $feature): bool
    {
        $features = $this->features ?? [];

        return in_array($feature, $features);
    }

    /**
     * Add a feature to the user.
     */
    public function addFeature(string $feature): void
    {
        $features = $this->features ?? [];
        if (! in_array($feature, $features)) {
            $features[] = $feature;
            $this->features = $features;
        }
    }

    /**
     * Remove a feature from the user.
     */
    public function removeFeature(string $feature): void
    {
        $features = $this->features ?? [];
        $this->features = array_values(array_filter($features, fn ($f) => $f !== $feature));
    }

    /**
     * Get all available features for partners.
     *
     * @return array<string, string>
     */
    public static function getAvailableFeatures(): array
    {
        return [
            'advanced_analytics' => 'Advanced Analytics',
            'custom_branding' => 'Custom Branding',
            'api_access' => 'API Access',
            'bulk_upload' => 'Bulk Upload Shows',
            'export_data' => 'Export Data',
            'custom_domain' => 'Custom Domain',
            'priority_support' => 'Priority Support',
            'unlimited_shows' => 'Unlimited Shows',
            'white_label' => 'White Label',
            'advanced_scheduling' => 'Advanced Scheduling',
            'multi_language' => 'Multi Language Support',
            'custom_integrations' => 'Custom Integrations',
        ];
    }

    /**
     * Get the API keys for this user.
     */
    public function apiKeys(): HasMany
    {
        return $this->hasMany(ApiKey::class);
    }

    /**
     * Get the active API key for this user.
     */
    public function activeApiKey()
    {
        return $this->apiKeys()
            ->where('is_active', true)
            ->where(function ($query) {
                $query->whereNull('expires_at')
                    ->orWhere('expires_at', '>', now());
            })
            ->first();
    }

    /**
     * Check if user can create more API keys (max 1 for partners).
     */
    public function canCreateApiKey(): bool
    {
        if (! $this->isPartner()) {
            return false;
        }

        // Partner maksimal 1 API key
        return $this->apiKeys()->count() < 1;
    }

    /**
     * Get the coin currency name.
     */
    public static function getCoinName(): string
    {
        return 'NgidoliCoin';
    }

    /**
     * Get the coin currency abbreviation.
     */
    public static function getCoinAbbreviation(): string
    {
        return 'NGD';
    }

    /**
     * Add coins to user balance.
     */
    public function addCoins(int $amount): bool
    {
        if ($amount <= 0) {
            return false;
        }

        $this->coin_balance = ($this->coin_balance ?? 0) + $amount;

        return $this->save();
    }

    /**
     * Deduct coins from user balance.
     */
    public function deductCoins(int $amount): bool
    {
        if ($amount <= 0) {
            return false;
        }

        if (! $this->hasEnoughCoins($amount)) {
            return false;
        }

        $this->coin_balance = max(0, ($this->coin_balance ?? 0) - $amount);

        return $this->save();
    }

    /**
     * Check if user has enough coins.
     */
    public function hasEnoughCoins(int $amount): bool
    {
        return ($this->coin_balance ?? 0) >= $amount;
    }

    /**
     * Get current coin balance.
     */
    public function getCoinBalance(): int
    {
        return $this->coin_balance ?? 0;
    }

    /**
     * Check if user can purchase show with coins.
     */
    public function canPurchaseShowWithCoins(Show $show): bool
    {
        $coinPrice = $show->getCoinPrice();

        if ($coinPrice === null || $coinPrice <= 0) {
            return false;
        }

        return $this->hasEnoughCoins($coinPrice);
    }
}
