<?php

declare(strict_types=1);

namespace App\Services;

use App\Core\Database;
use App\Helpers\UploadHelper;
use App\Models\UserModel;

/**
 * UserService
 *
 * Orchestrates the multi-step operations behind the Admin > Users
 * pages that a plain UserModel::insert()/update() call isn't enough
 * for on its own: inviting a new user (creates the row + sends the
 * invitation token), assigning business-line access, deactivating
 * (which also revokes every active session), and an admin-triggered
 * password reset (distinct from the user's own "forgot password" flow
 * — see PasswordResetService for that one).
 */
final class UserService
{
    public function __construct(
        private readonly UserModel $users = new UserModel(),
        private readonly SessionService $sessions = new SessionService(),
        private readonly MailService $mailer = new MailService()
    ) {
    }

    /**
     * Creates a pending user (status='invited', no usable password yet)
     * and emails them a one-time link to set their own password —
     * matches the "Invite User" modal on the User Management page.
     */
    public function invite(array $data, int $invitedByUserId): int
    {
        $userId = $this->users->insert([
            'role_id' => $data['role_id'] ?? null,
            'department_id' => $data['department_id'] ?? null,
            'first_name' => $data['first_name'],
            'last_name' => $data['last_name'] ?? '',
            'email' => $data['email'],
            'phone' => $data['phone'] ?? null,
            'job_title' => $data['job_title'] ?? null,
            // Placeholder hash — unusable until the invite is accepted
            // and PasswordPolicyService::commit() sets a real one.
            'password_hash' => password_hash(bin2hex(random_bytes(32)), PASSWORD_DEFAULT),
            'status' => 'invited',
        ], $invitedByUserId);

        $this->syncBusinessLines($userId, $data['business_line_ids'] ?? []);

        $token = bin2hex(random_bytes(32));
        $pdo = Database::connection();
        $pdo->prepare(
            'INSERT INTO user_invitations (user_id, token_hash, invited_by, expires_at, created_at)
             VALUES (:user_id, :hash, :invited_by, :expires, NOW())'
        )->execute([
            'user_id' => $userId,
            'hash' => hash('sha256', $token),
            'invited_by' => $invitedByUserId,
            'expires' => date('Y-m-d H:i:s', time() + (7 * 86400)), // 7 days
        ]);

        $inviteUrl = base_url('accept-invitation/' . $token);
        $this->mailer->send(
            $data['email'],
            $data['first_name'],
            "You've been invited to A&B Hub",
            "<p>Hi {$data['first_name']},</p>"
            . '<p>You have been invited to join A&B Hub. Set your password to get started:</p>'
            . "<p><a href=\"{$inviteUrl}\">{$inviteUrl}</a></p>"
            . '<p>This invitation expires in 7 days.</p>'
        );

        ActivityLogger::record('user', $userId, 'user_invited', "Invited {$data['email']}", category: 'admin');

        return $userId;
    }

    public function update(int $userId, array $data, int $actingUserId): void
    {
        $this->users->update($userId, [
            'role_id' => $data['role_id'] ?? null,
            'department_id' => $data['department_id'] ?? null,
            'first_name' => $data['first_name'],
            'last_name' => $data['last_name'] ?? '',
            'phone' => $data['phone'] ?? null,
            'job_title' => $data['job_title'] ?? null,
        ], $actingUserId);

        if (array_key_exists('business_line_ids', $data)) {
            $this->syncBusinessLines($userId, $data['business_line_ids']);
        }

        ActivityLogger::record('user', $userId, 'user_updated', 'User profile updated', category: 'admin');
    }

    public function updateAvatar(int $userId, array $uploadedFile): array
    {
        $uploader = UploadHelper::make();
        $result = $uploader->store($uploadedFile, 'avatars');

        if ($result === null) {
            return ['success' => false, 'errors' => $uploader->errors()];
        }

        $this->users->update($userId, ['avatar_path' => $result['disk_path']]);

        return ['success' => true, 'path' => $result['disk_path']];
    }

    public function deactivate(int $userId, int $actingUserId): void
    {
        $this->users->update($userId, ['status' => 'suspended'], $actingUserId);
        $this->sessions->revokeAll($userId);

        ActivityLogger::record('user', $userId, 'user_deactivated', 'User account deactivated', category: 'admin');
    }

    public function reactivate(int $userId, int $actingUserId): void
    {
        $this->users->update($userId, ['status' => 'active'], $actingUserId);

        ActivityLogger::record('user', $userId, 'user_reactivated', 'User account reactivated', category: 'admin');
    }

    /** Admin-triggered reset: generates a new temporary password, forces a change on next login via a reset token — never emails a plaintext password. */
    public function adminResetPassword(int $userId, int $actingUserId): void
    {
        $pdo = Database::connection();
        $stmt = $pdo->prepare('SELECT email, first_name FROM users WHERE id = :id');
        $stmt->execute(['id' => $userId]);
        $user = $stmt->fetch();

        if ($user === false) {
            throw new \RuntimeException('User not found.');
        }

        // Reuse the same secure token mechanism as a self-service reset
        // rather than generating a password an admin would have to relay
        // insecurely.
        (new PasswordResetService())->sendResetLink($user['email']);
        $this->sessions->revokeAll($userId);

        ActivityLogger::record('user', $userId, 'admin_password_reset', 'Administrator triggered a password reset', category: 'admin');
    }

    public function syncBusinessLines(int $userId, array $businessLineIds): void
    {
        $pdo = Database::connection();
        $pdo->prepare('DELETE FROM user_business_lines WHERE user_id = :id')->execute(['id' => $userId]);

        if ($businessLineIds === []) {
            return;
        }

        $stmt = $pdo->prepare('INSERT INTO user_business_lines (user_id, business_line_id) VALUES (:user_id, :bl_id)');
        foreach (array_unique(array_map('intval', $businessLineIds)) as $blId) {
            $stmt->execute(['user_id' => $userId, 'bl_id' => $blId]);
        }
    }
}
