<?php

/** @noinspection PhpUnhandledExceptionInspection */

namespace DefStudio\GameEngine\Models\Concerns;

use DefStudio\GameEngine\Enums\Role;
use Illuminate\Support\Facades\Gate;
use DefStudio\GameEngine\Models\User;
use DefStudio\GameEngine\Models\Organization;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use DefStudio\GameEngine\Exceptions\OrganizationException;
use DefStudio\GameEngine\Events\Organizations\UserSwitchedOrganization;
use DefStudio\GameEngine\Events\Organizations\UserSubscribedToOrganization;
use DefStudio\GameEngine\Events\Organizations\UserUnsubscribedFromOrganization;

trait HasOrganizations
{
    /**
     * @return BelongsToMany<Organization>
     */
    public function organizations(): BelongsToMany
    {
        return $this->belongsToMany(\game_engine()->organizationClass(), config('game-engine.organizations.subscriptions_table'))
            ->withPivot('role')
            ->withTimestamps();
    }

    /**
     * @return BelongsTo<Organization, User>
     */
    public function current_organization(): BelongsTo
    {
        return $this->belongsTo(\game_engine()->organizationClass());
    }

    public function is_current_organization(Organization|int $organization): bool
    {
        if ($this->current_organization_id === null) {
            return false;
        }

        $organization_id = is_int($organization) ? $organization : $organization->id;

        return $this->current_organization_id === $organization_id;
    }

    public function switch_organization(Organization $organization): bool
    {
        if ($this->hasRole(Role::super_admin)) {
            $this->forceFill(['current_organization_id' => $organization->id])->save();
            $this->unsetRelation('current_organization');
            UserSwitchedOrganization::dispatch($this, $organization);

            return true;
        }

        Gate::forUser($this)->authorize('switch', $organization);

        $this->forceFill(['current_organization_id' => $organization->id])->save();
        $this->unsetRelation('current_organization');

        $this->roles()->detach();
        $this->assignRole($this->get_role_in_organization($organization)->value);

        UserSwitchedOrganization::dispatch($this, $organization);

        return true;
    }

    public function get_role_in_organization(Organization $organization): Role
    {
        $this->ensure_belongs_to_organization($organization);

        return Role::from($this->organizations->find($organization->id)->pivot->role);
    }

    public function ensure_belongs_to_organization(Organization $organization): static
    {
        throw_if(!$this->belongs_to_organization($organization), OrganizationException::organization_membership_failure());

        return $this;
    }

    public function belongs_to_organization(Organization $organization): bool
    {
        return $this->has_attached($organization);
    }

    public function subscribe_to_organization(Organization $organization, Role $role, bool $switch = false): static
    {
        throw_if(!in_array($role, [Role::admin, Role::master, Role::player]), OrganizationException::invalid_role($role));
        throw_if($this->belongs_to_organization($organization), OrganizationException::already_subscribed());

        $this->organizations()->save($organization, ['role' => $role->value]);

        UserSubscribedToOrganization::dispatch($this, $organization);

        if ($switch) {
            $this->switch_organization($organization);
        }

        return $this;
    }

    public function unsubscribe_from_organization(Organization $organization): static
    {
        throw_if(!$this->belongs_to_organization($organization), OrganizationException::organization_membership_failure());

        $this->organizations()->detach($organization);

        UserUnsubscribedFromOrganization::dispatch($this, $organization);

        return $this;
    }
}
