<?php

namespace App\Services\Parents;

use App\Models\AuthorizedUser;
use App\Models\User;
use App\Services\ApplicationUrlService;
use App\Services\Email\EmailDispatchService;
use Illuminate\Support\Facades\DB;

/**
 * legacy {@see \App\Controllers\View\AuthorizedUsersController} parity.
 */
class AuthorizedUsersManagementService
{
    private const TOKEN_TTL_HOURS = 24;

    public function __construct(
        private EmailDispatchService $mail,
        private ApplicationUrlService $urls,
    ) {}

    public function hashToken(string $plain): string
    {
        return hash('sha256', $plain);
    }

    /**
     * legacy create() first step — lookup by token after invite email (pending row, created within TTL).
     *
     * Tokens are stored as SHA-256 hashes; we only ever look up by hash so that
     * a leaked database row cannot be replayed as a plaintext token.
     */
    public function findPendingByIncomingToken(string $plainToken): ?AuthorizedUser
    {
        if ($plainToken === '') {
            return null;
        }

        return AuthorizedUser::query()
            ->where('token', $this->hashToken($plainToken))
            ->where('created_at', '>=', now()->subHours(self::TOKEN_TTL_HOURS))
            ->first();
    }

    /**
     * legacy setPassword / savePassword — active row with rotating token.
     */
    public function findActiveSetupRow(int $authorizedUserId, string $plainToken): ?AuthorizedUser
    {
        if ($plainToken === '' || $authorizedUserId <= 0) {
            return null;
        }

        return AuthorizedUser::query()
            ->where('token', $this->hashToken($plainToken))
            ->where('authorized_user_id', $authorizedUserId)
            ->where('status', 'Active')
            ->where('updated_at', '>=', now()->subHours(self::TOKEN_TTL_HOURS))
            ->first();
    }

    /**
     * @return array{redirect_url: string}
     */
    public function confirmEmailClick(string $plainToken): array
    {
        $row = $this->findPendingByIncomingToken($plainToken);
        if (! $row) {
            throw new \InvalidArgumentException('Invalid or expired confirmation link.');
        }

        $nextPlain = bin2hex(random_bytes(48));
        $nextHash = $this->hashToken($nextPlain);

        $row->status = 'Active';
        $row->token = $nextHash;
        $row->save();

        $url = $this->urls->inviteSetPasswordFormUrl((int) $row->authorized_user_id, $nextPlain);

        return ['redirect_url' => $url];
    }

    /**
     * legacy `create` — when email is not a registered user, respond success without leaking (privacy).
     *
     * Refuses self-invites and silently re-uses an existing invite row to avoid
     * letting a parent spam another user with confirmation emails.
     *
     * @return array{created: bool, record: AuthorizedUser|null}
     */
    public function inviteByEmail(int $primaryUserId, string $email): array
    {
        $email = strtolower(trim($email));

        $user = User::query()
            ->whereRaw('LOWER(email) = ?', [$email])
            ->first();

        if (! $user) {
            return ['created' => false, 'record' => null];
        }

        // A parent cannot invite themselves as their own authorized user.
        if ((int) $user->id === $primaryUserId) {
            return ['created' => false, 'record' => null];
        }

        // If the same parent already invited this user, rotate the token instead
        // of creating a duplicate row (prevents spam + race-driven duplicates).
        $existing = AuthorizedUser::query()
            ->where('user_id', $primaryUserId)
            ->where('authorized_user_id', (int) $user->id)
            ->first();

        $plain = bin2hex(random_bytes(48));
        $tokenHash = $this->hashToken($plain);

        $phone = trim((string) ($user->cellphone ?? ''));
        $gender = trim((string) ($user->gender ?? ''));

        if ($existing) {
            $existing->update([
                'firstname' => (string) ($user->firstname ?? ''),
                'lastname' => (string) ($user->lastname ?? ''),
                'phone_number' => $phone !== '' ? $phone : '-',
                'gender' => $gender !== '' ? $gender : '-',
                'email' => $email,
                'token' => $tokenHash,
                'status' => 'Pending',
            ]);
            $record = $existing;
        } else {
            $record = AuthorizedUser::query()->create([
                'user_id' => $primaryUserId,
                'authorized_user_id' => (int) $user->id,
                'firstname' => (string) ($user->firstname ?? ''),
                'lastname' => (string) ($user->lastname ?? ''),
                'phone_number' => $phone !== '' ? $phone : '-',
                'gender' => $gender !== '' ? $gender : '-',
                'email' => $email,
                'relation_to_user' => null,
                'token' => $tokenHash,
                'status' => 'Pending',
            ]);
        }

        $this->sendConfirmationEmail($email, $plain);

        return ['created' => true, 'record' => $record];
    }

    public function sendConfirmationEmail(string $email, string $plainToken): void
    {
        $confirmLink = $this->urls->inviteConfirmUrl($plainToken);
        $body = '<p>You have been added as an authorized user for another account. Click the link below to confirm your access:</p>'
            .'<p><a href="'.e($confirmLink).'">Confirm Access</a></p>'
            .'<p>If you did not request this, please ignore this email.</p>';

        $this->mail->send($email, 'Authorized User Confirmation', $body, 'notifications');
    }

    /**
     * @throws \InvalidArgumentException
     */
    public function setAuthorizedUserPassword(int $authorizedUserId, int $postedUserId, string $plainToken, string $password): void
    {
        if ($postedUserId !== $authorizedUserId || $authorizedUserId <= 0) {
            throw new \InvalidArgumentException('Invalid request.');
        }

        $row = $this->findActiveSetupRow($authorizedUserId, $plainToken);
        if (! $row) {
            throw new \InvalidArgumentException('Invalid or expired confirmation link.');
        }

        $user = User::query()->find($authorizedUserId);
        if (! $user) {
            throw new \InvalidArgumentException('User not found.');
        }

        DB::transaction(function () use ($user, $row, $password) {
            $user->password = $password;
            $user->save();

            $row->token = null;
            $row->save();
        });
    }
}
