<?php

namespace App\Livewire\Team;

use App\Models\TeamInvitation;
use App\Models\User;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Notifications\Messages\MailMessage;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Livewire\Component;

class InviteLink extends Component
{
    use AuthorizesRequests;

    public string $email;

    public string $role = 'member';

    protected $rules = [
        'email' => 'required|email',
        'role' => 'required|string',
    ];

    public function mount()
    {
        $this->email = isDev() ? 'test3@example.com' : '';
    }

    public function viaEmail()
    {
        $this->generateInviteLink(sendEmail: true);
    }

    public function viaLink()
    {
        $this->generateInviteLink(sendEmail: false);
    }

    private function invitationUrl(string $routeName, array $parameters): string
    {
        $fqdn = instanceSettings()->fqdn;
        if (filled($fqdn)) {
            return rtrim($fqdn, '/').route($routeName, $parameters, false);
        }

        return route($routeName, $parameters);
    }

    private function generateInviteLink(bool $sendEmail = false)
    {
        try {
            $this->authorize('manageInvitations', currentTeam());
            $this->validate();

            // Prevent privilege escalation: users cannot invite someone with higher privileges
            $userRole = auth()->user()->role();
            if (is_null($userRole) || ($userRole === 'member' && in_array($this->role, ['admin', 'owner']))) {
                throw new \Exception('Members cannot invite admins or owners.');
            }
            if ($userRole === 'admin' && $this->role === 'owner') {
                throw new \Exception('Admins cannot invite owners.');
            }

            $this->email = strtolower($this->email);

            $member_emails = currentTeam()->members()->get()->pluck('email');
            if ($member_emails->contains($this->email)) {
                return handleError(livewire: $this, customErrorMessage: "$this->email is already a member of ".currentTeam()->name.'.');
            }
            $uuid = new_public_id(32);
            $link = $this->invitationUrl('team.invitation.show', ['uuid' => $uuid]);
            $user = User::whereEmail($this->email)->first();

            if (is_null($user)) {
                $password = Str::password();
                $user = User::create([
                    'name' => str($this->email)->before('@'),
                    'email' => $this->email,
                    'password' => Hash::make($password),
                    'force_password_reset' => true,
                ]);
                $token = Crypt::encryptString("{$user->email}@@@{$uuid}@@@{$password}");
                $link = $this->invitationUrl('auth.link', ['token' => $token]);
            }
            $invitation = TeamInvitation::whereEmail($this->email)->first();
            if (! is_null($invitation)) {
                $invitationValid = $invitation->isValid();
                if ($invitationValid) {
                    return handleError(livewire: $this, customErrorMessage: "Pending invitation already exists for $this->email.");
                } else {
                    $invitation->delete();
                }
            }

            $invitation = TeamInvitation::firstOrCreate([
                'team_id' => currentTeam()->id,
                'uuid' => $uuid,
                'email' => $this->email,
                'role' => $this->role,
                'link' => $link,
                'via' => $sendEmail ? 'email' : 'link',
            ]);
            if ($sendEmail) {
                $mail = new MailMessage;
                $mail->view('emails.invitation-link', [
                    'team' => currentTeam()->name,
                    'invitation_link' => $link,
                ]);
                $mail->subject('You have been invited to '.currentTeam()->name.' on '.config('app.name').'.');
                send_user_an_email($mail, $this->email);
                $this->dispatch('success', 'Invitation sent via email.');
                $this->dispatch('refreshInvitations');

                return;
            } else {
                $this->dispatch('success', 'Invitation link generated.');
                $this->dispatch('refreshInvitations');
            }
        } catch (\Throwable $e) {
            $error_message = $e->getMessage();
            if ($e->getCode() === '23505') {
                $error_message = 'Invitation already sent.';
            }

            return handleError(error: $e, livewire: $this, customErrorMessage: $error_message);
        }
    }
}
