<?php

declare(strict_types=1);

namespace App\Services;

use App\Models\User;
use App\Models\UserInvitation;

class UserService
{
    private User $userModel;
    private UserInvitation $invitationModel;
    private MailService $mailService;

    public function __construct()
    {
        $this->userModel = new User();
        $this->invitationModel = new UserInvitation();
        $this->mailService = new MailService();
    }

    public function getUsers(array $filters = [], int $page = 1, int $perPage = 15): array
    {
        $offset = ($page - 1) * $perPage;

        $where = [];
        $params = [];

        if (!empty($filters['search'])) {
            $where[] = "(name LIKE :search1 OR email LIKE :search2)";
            $params['search1'] = '%' . $filters['search'] . '%';
            $params['search2'] = '%' . $filters['search'] . '%';
        }

        if (!empty($filters['role'])) {
            $where[] = "role = :role";
            $params['role'] = $filters['role'];
        }

        if (!empty($filters['status'])) {
            $where[] = "status = :status";
            $params['status'] = $filters['status'];
        }

        $whereClause = !empty($where) ? 'WHERE ' . implode(' AND ', $where) : '';

        $total = (int) $this->userModel->queryOne(
            "SELECT COUNT(*) as cnt FROM users {$whereClause}",
            $params
        )['cnt'];

        $items = $this->userModel->query(
            "SELECT id, name, email, role, status, current_event_id, created_at, updated_at
             FROM users {$whereClause}
             ORDER BY created_at DESC
             LIMIT :limit OFFSET :offset",
            array_merge($params, ['limit' => $perPage, 'offset' => $offset])
        );

        return [
            'items' => $items,
            'total' => $total,
            'page' => $page,
            'per_page' => $perPage,
            'total_pages' => (int) ceil($total / $perPage),
        ];
    }

    public function getUser(int $id): array
    {
        $user = $this->userModel->findById($id);
        if (!$user) {
            throw new \RuntimeException('Usuario no encontrado', 404);
        }

        unset($user['password']);
        return $user;
    }

    public function createUser(array $data, int $invitedBy): array
    {
        $existing = $this->userModel->findByEmail($data['email']);
        if ($existing) {
            throw new \RuntimeException('El correo electrónico ya está registrado', 409);
        }

        $tempPassword = $this->generatePassword();

        $userId = $this->userModel->createUser([
            'name' => $data['name'],
            'email' => $data['email'],
            'password' => $tempPassword,
            'role' => $data['role'],
            'status' => 'pending_activation',
        ]);

        $token = bin2hex(random_bytes(32));
        $this->invitationModel->invalidateExistingForEmail($data['email']);
        $this->invitationModel->createInvitation($data['email'], $token, $data['role'], $invitedBy, 86400);

        $this->mailService->sendUserInvitation($data['email'], $token, $data['role'], $tempPassword);

        return $this->getUser($userId);
    }

    public function updateUser(int $id, array $data): array
    {
        $user = $this->userModel->findById($id);
        if (!$user) {
            throw new \RuntimeException('Usuario no encontrado', 404);
        }

        $updateData = [];
        if (isset($data['name'])) {
            $updateData['name'] = $data['name'];
        }
        if (isset($data['email']) && $data['email'] !== $user['email']) {
            $existing = $this->userModel->findByEmail($data['email']);
            if ($existing && (int) $existing['id'] !== $id) {
                throw new \RuntimeException('El correo electrónico ya está registrado por otro usuario', 409);
            }
            $updateData['email'] = $data['email'];
        }
        if (isset($data['role'])) {
            $updateData['role'] = $data['role'];
        }

        if (!empty($data['password'])) {
            if (strlen($data['password']) < 8) {
                throw new \RuntimeException('La contraseña debe tener al menos 8 caracteres', 422);
            }
            $this->userModel->updatePassword($id, $data['password']);
        }

        if (!empty($updateData)) {
            $this->userModel->update($id, $updateData);
        }

        return $this->getUser($id);
    }

    public function deleteUser(int $id): void
    {
        $user = $this->userModel->findById($id);
        if (!$user) {
            throw new \RuntimeException('Usuario no encontrado', 404);
        }
        $this->userModel->delete($id);
    }

    public function toggleStatus(int $id): array
    {
        $user = $this->userModel->findById($id);
        if (!$user) {
            throw new \RuntimeException('Usuario no encontrado', 404);
        }

        $newStatus = $user['status'] === 'active' ? 'inactive' : 'active';
        $this->userModel->update($id, ['status' => $newStatus]);

        return $this->getUser($id);
    }

    public function resendInvitation(int $id, int $invitedBy): array
    {
        $user = $this->userModel->findById($id);
        if (!$user) {
            throw new \RuntimeException('Usuario no encontrado', 404);
        }

        if ($user['status'] !== 'pending_activation') {
            throw new \RuntimeException('El usuario ya ha sido activado', 400);
        }

        $newPassword = $this->generatePassword();
        $this->userModel->updatePassword($id, $newPassword);

        $token = bin2hex(random_bytes(32));
        $this->invitationModel->invalidateExistingForEmail($user['email']);
        $this->invitationModel->createInvitation($user['email'], $token, $user['role'], $invitedBy, 86400);

        $this->mailService->sendUserInvitation($user['email'], $token, $user['role'], $newPassword);

        return $this->getUser($id);
    }

    private function generatePassword(): string
    {
        $upper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
        $lower = 'abcdefghijklmnopqrstuvwxyz';
        $digits = '0123456789';
        $special = '!@#$%';
        $all = $upper . $lower . $digits . $special;

        $password = '';
        $password .= $upper[random_int(0, strlen($upper) - 1)];
        $password .= $lower[random_int(0, strlen($lower) - 1)];
        $password .= $digits[random_int(0, strlen($digits) - 1)];
        $password .= $special[random_int(0, strlen($special) - 1)];

        for ($i = 0; $i < 6; $i++) {
            $password .= $all[random_int(0, strlen($all) - 1)];
        }

        return str_shuffle($password);
    }
}
