<?php

namespace Wave;

use Exception;
use Stripe\StripeClient;
use Carbon\Carbon;
use Devdojo\Auth\Models\User as AuthUser;
use Filament\Models\Contracts\FilamentUser;
use Filament\Models\Contracts\HasAvatar;
use Filament\Panel;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Lab404\Impersonate\Models\Impersonate;
use Spatie\Permission\Traits\HasRoles;
use Tymon\JWTAuth\Contracts\JWTSubject;

class User extends AuthUser implements FilamentUser, HasAvatar, JWTSubject
{
    use HasRoles, Impersonate, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var array<int, string>
     */
    protected $fillable = [
        'name',
        'email',
        'username',
        'avatar',
        'password',
        'role_id',
        'verification_code',
        'verified',
        'trial_ends_at',
    ];

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

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'trial_ends_at' => 'datetime',
        ];
    }

    public function onTrial()
    {
        if (is_null($this->trial_ends_at)) {
            return false;
        }
        if ($this->subscriber()) {
            return false;
        }

        return true;
    }

    public function subscriptions(): HasMany
    {
        return $this->hasMany(Subscription::class, 'billable_id')->where('billable_type', 'user');
    }

    public function subscriber()
    {
        // Use cache if available, otherwise direct query
        if (app()->bound('cache')) {
            try {
                return Cache::remember("user_subscriber_{$this->id}", 300, function () {
                    return $this->subscriptions()->where('status', 'active')->exists();
                });
            } catch (Exception $e) {
                // Fallback to direct query if cache fails
            }
        }

        return $this->subscriptions()->where('status', 'active')->exists();
    }

    public function subscribedToPlan($planSlug)
    {
        // Use cache if available, otherwise direct query
        if (app()->bound('cache')) {
            try {
                return Cache::remember("user_plan_{$this->id}_{$planSlug}", 300, function () use ($planSlug) {
                    $plan = Plan::getByName($planSlug);
                    if (! $plan) {
                        return false;
                    }

                    return $this->subscriptions()->where('plan_id', $plan->id)->where('status', 'active')->exists();
                });
            } catch (Exception $e) {
                // Fallback to direct query if cache fails
            }
        }

        $plan = Plan::where('name', $planSlug)->first();
        if (! $plan) {
            return false;
        }

        return $this->subscriptions()->where('plan_id', $plan->id)->where('status', 'active')->exists();
    }

    public function plan()
    {
        $latest_subscription = $this->latestSubscription();

        return Plan::find($latest_subscription->plan_id);
    }

    public function planInterval()
    {
        $latest_subscription = $this->latestSubscription();

        return ($latest_subscription->cycle == 'month') ? 'Monthly' : 'Yearly';
    }

    public function latestSubscription()
    {
        return $this->subscriptions()->where('status', 'active')->orderByDesc('created_at')->first();
    }

    public function subscription(): HasOne
    {
        return $this->hasOne(Subscription::class, 'billable_id')->where('status', 'active')->orderByDesc('created_at');
    }

    public function switchPlans(Plan $plan)
    {
        $this->syncRoles([]);
        $this->assignRole($plan->role->name);
    }

    public function invoices()
    {
        $user_invoices = [];

        if (is_null($this->subscription)) {
            return null;
        }

        if (config('wave.billing_provider') == 'stripe') {
            $stripe = new StripeClient(config('wave.stripe.secret_key'));
            $subscriptions = $this->subscriptions()->get();
            foreach ($subscriptions as $subscription) {
                $invoices = $stripe->invoices->all(['customer' => $subscription->vendor_customer_id, 'limit' => 100]);

                foreach ($invoices as $invoice) {
                    array_push($user_invoices, (object) [
                        'id' => $invoice->id,
                        'created' => Carbon::parse($invoice->created)->isoFormat('MMMM Do YYYY, h:mm:ss a'),
                        'total' => number_format(($invoice->total / 100), 2, '.', ' '),
                        'download' => $invoice->invoice_pdf,
                    ]);
                }
            }
        } else {
            $paddle_url = (config('wave.paddle.env') == 'sandbox') ? 'https://sandbox-api.paddle.com' : 'https://api.paddle.com';
            $response = Http::withToken(config('wave.paddle.api_key'))->get($paddle_url.'/transactions', [
                'subscription_id' => $this->subscription->vendor_subscription_id,
            ]);
            $responseJson = json_decode($response->body());
            foreach ($responseJson->data as $invoice) {
                array_push($user_invoices, (object) [
                    'id' => $invoice->id,
                    'created' => Carbon::parse($invoice->created_at)->isoFormat('MMMM Do YYYY, h:mm:ss a'),
                    'total' => number_format(($invoice->details->totals->subtotal / 100), 2, '.', ' '),
                    'download' => '/settings/invoices/'.$invoice->id,
                ]);
            }
        }

        return $user_invoices;
    }

    public function canImpersonate(): bool
    {
        // If user is admin they can impersonate
        return $this->hasRole('admin');
    }

    public function isAdmin(): bool
    {
        // Use cache if available, otherwise direct query
        if (app()->bound('cache')) {
            try {
                return Cache::remember("user_admin_{$this->id}", 600, function () {
                    return $this->hasRole('admin');
                });
            } catch (Exception $e) {
                // Fallback to direct query if cache fails
            }
        }

        return $this->hasRole('admin');
    }

    public function canBeImpersonated(): bool
    {
        // Any user that is not an admin can be impersonated
        return ! $this->hasRole('admin');
    }

    public function hasChangelogNotifications()
    {
        // Get the latest Changelog
        $latest_changelog = Changelog::orderByDesc('created_at')->first();

        if (! $latest_changelog) {
            return false;
        }

        return ! $this->changelogs->contains($latest_changelog->id);
    }

    public function link()
    {
        return url('/profile/'.$this->username);
    }

    public function changelogs(): BelongsToMany
    {
        return $this->belongsToMany('Wave\Changelog');
    }

    public function createApiKey($name)
    {
        return ApiKey::create(['user_id' => $this->id, 'name' => $name, 'key' => Str::random(60)]);
    }

    public function apiKeys(): HasMany
    {
        return $this->hasMany('Wave\ApiKey')->orderByDesc('created_at');
    }

    /**
     * Clear user-related caches when data changes
     */
    public function clearUserCache()
    {
        // Only clear cache if it's available
        if (app()->bound('cache')) {
            try {
                Cache::forget("user_subscriber_{$this->id}");
                Cache::forget("user_admin_{$this->id}");

                // Clear plan-specific caches
                $plans = Plan::pluck('name');
                foreach ($plans as $planName) {
                    Cache::forget("user_plan_{$this->id}_{$planName}");
                }
            } catch (Exception $e) {
                // Silently handle cache clearing failures
            }
        }
    }

    public function avatar()
    {
        return Storage::url($this->avatar);
    }

    /**
     * Get the identifier that will be stored in the subject claim of the JWT.
     *
     * @return mixed
     */
    public function getJWTIdentifier()
    {
        return $this->getKey();
    }

    /**
     * Return a key value array, containing any custom claims to be added to the JWT.
     */
    public function getJWTCustomClaims(): array
    {
        return [];
    }

    public function getFilamentAvatarUrl(): ?string
    {
        return $this->avatar();
    }

    public function profile($key)
    {
        $keyValue = $this->profileKeyValue($key);

        return isset($keyValue->value) ? $keyValue->value : '';
    }

    public function canAccessPanel(Panel $panel): bool
    {
        if ($panel->getId() === 'admin' && auth()->user()->hasRole('admin')) {
            return true;
        }

        return false;
    }

    /*** PUT ALL THESE below into a trait */

    /**
     * Return default User Role.
     */
    // public function role()
    // {
    //     return $this->belongsTo(Role::class);
    // }

    // /**
    //  * Return alternative User Roles.
    //  */
    // public function roles()
    // {
    //     return $this->belongsToMany(Role::class, 'user_roles', 'user_id', 'role_id');
    // }

    // /**
    //  * Return all User Roles, merging the default and alternative roles.
    //  */
    // public function roles_all()
    // {
    //     $this->loadRolesRelations();

    //     return collect([$this->role])->merge($this->roles);
    // }

    // /**
    //  * Check if User has a Role(s) associated.
    //  *
    //  * @param string|array $name The role(s) to check.
    //  *
    //  * @return bool
    //  */
    // public function hasRole($name)
    // {
    //     $roles = $this->roles_all()->pluck('name')->toArray();

    //     foreach ((is_array($name) ? $name : [$name]) as $role) {
    //         if (in_array($role, $roles)) {
    //             return true;
    //         }
    //     }

    //     return false;
    // }

    // /**
    //  * Set default User Role.
    //  *
    //  * @param string $name The role name to associate.
    //  */
    // public function setRole($name)
    // {
    //     $role = Role::where('name', '=', $name)->first();

    //     if ($role) {
    //         $this->role()->associate($role);
    //         $this->save();
    //     }

    //     return $this;
    // }

    // public function hasPermission($name)
    // {
    //     $this->loadPermissionsRelations();

    //     $_permissions = $this->roles_all()
    //                           ->pluck('permissions')->flatten()
    //                           ->pluck('key')->unique()->toArray();

    //     return in_array($name, $_permissions);
    // }

    // public function hasPermissionOrFail($name)
    // {
    //     if (!$this->hasPermission($name)) {
    //         throw new UnauthorizedHttpException(null);
    //     }

    //     return true;
    // }

    // public function hasPermissionOrAbort($name, $statusCode = 403)
    // {
    //     if (!$this->hasPermission($name)) {
    //         return abort($statusCode);
    //     }

    //     return true;
    // }

    // private function loadRolesRelations()
    // {
    //     if (!$this->relationLoaded('role')) {
    //         $this->load('role');
    //     }

    //     if (!$this->relationLoaded('roles')) {
    //         $this->load('roles');
    //     }
    // }

    // private function loadPermissionsRelations()
    // {
    //     $this->loadRolesRelations();

    //     if ($this->role && !$this->role->relationLoaded('permissions')) {
    //         $this->role->load('permissions');
    //         $this->load('roles.permissions');
    //     }
    // }
}
